# SAM.gov Bid Finder for Small Contractors (`edge_works/sam-gov-bid-fit-finder`) Actor

Find open federal contract opportunities in the public SAM.gov extract. Filter by keyword, NAICS, set-aside, state, and agency. Optional Grok bid-fit score. Contacts removed.

- **URL**: https://apify.com/edge_works/sam-gov-bid-fit-finder.md
- **Developed by:** [Ryan Kirkendall](https://apify.com/edge_works) (community)
- **Categories:** AI, Business, Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 opportunity returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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 Bid Finder

Finds open federal contract opportunities from the public SAM.gov Contract Opportunities extract and returns the ones that match your filters, best matches first.

It is for small contractors and bid teams looking for federal work they can still respond to. It does not log in to SAM.gov and it does not use a SAM.gov API key.

### What it does

1. Downloads the public daily Contract Opportunities file that SAM.gov publishes for data.gov.
2. Drops notices that are already past, or too close to, the response deadline, and drops notice types you did not ask for.
3. Keeps notices that match your keywords (whole word, in the title or description), NAICS prefix, set-aside, place-of-performance state, and agency.
4. Collapses amendments into one row per solicitation and ranks that row with a rule-based match score.
5. Optionally sends the top rows to Grok (xAI) with your company profile and adds a 0-100 bid-fit score.

The extract is updated about once a day, so a notice can take up to a day to appear. The Actor does not download solicitation attachments. Read the full notice on SAM.gov before you decide to bid.

### AI use

Grok scoring is off unless you turn it on.

- If you do not supply an xAI key, and scoring is on, the Actor uses the key configured by the Actor owner. Each scored opportunity is an "AI bid-fit score" event.
- If you put your own key in the secret input field `xaiApiKey`, Grok runs on your xAI account and that AI event is not charged.
- Each run also has a Grok spending cap (`maxGrokSpendUsd`). A call that could cross the cap is not sent.
- The model is asked to quote the notice. A quote that is not in the notice text is dropped (`evidenceVerified` is false).
- This is a screening aid. It is not procurement or legal advice. Short notices can come back as `insufficient_info`.

### Input example

```json
{
  "keywords": ["janitorial", "custodial"],
  "naicsCodes": ["561720"],
  "setAsides": ["SBA"],
  "placeOfPerformanceStates": ["OK"],
  "postedWithinDays": 30,
  "minDaysUntilDeadline": 3,
  "maxResults": 10,
  "aiFitScoring": true,
  "companyProfile": "Small Oklahoma-based janitorial and facility maintenance company, 15 employees, small business, no security clearances",
  "maxAiScored": 2,
  "maxGrokSpendUsd": 0.02
}
```

`companyProfile` needs at least 40 characters when AI scoring is on. Leave `xaiApiKey` empty to use the Actor's key, or set it to use your own.

### Output example

One row from a small test run (lists shortened). Contact details in the notice text appear as `[contact removed]`.

```json
{
  "noticeId": "50214e18baae4028a7fc4b3993ea0a6d",
  "title": "S--BGNDRF CUSTODIAL SERVICES",
  "solicitationNumber": "140RR027Q0001",
  "agency": "INTERIOR, DEPARTMENT OF THE",
  "subTier": "DEPARTMENTAL OFFICES",
  "office": "RECLAMATION CONTRACTING ACTIVITY",
  "noticeType": "Presolicitation",
  "baseType": "Presolicitation",
  "postedDate": "2026-10-07",
  "responseDeadline": "2026-10-22T13:00:00-06:00",
  "daysUntilDeadline": 14,
  "setAsideCode": "SBA",
  "setAside": "Small Business Set Aside - Total",
  "naicsCode": "561720",
  "pscCode": "S201",
  "placeOfPerformance": {
    "city": null,
    "state": null,
    "zip": null,
    "country": null
  },
  "samUrl": "https://sam.gov/workspace/contract/opp/50214e18baae4028a7fc4b3993ea0a6d/view",
  "additionalInfoLink": null,
  "keywordsInTitle": [
    "custodial"
  ],
  "keywordsInDescription": [
    "janitorial"
  ],
  "relatedNoticeIds": [],
  "matchScore": 51,
  "matchReasons": [
    "Keyword in title: custodial",
    "Keyword in description: janitorial",
    "14 days to respond"
  ],
  "aiFit": {
    "fitScore": 85,
    "verdict": "strong_fit",
    "summary": "The company's core business in janitorial and facility maintenance directly matches the NAICS 561720 and PSC S201 for custodial services at a government facility. It is a small business and the notice is a total small business set-aside.",
    "reasons": [
      "Exact match on janitorial/custodial services (NAICS 561720, PSC S201)",
      "Total Small Business Set-aside aligns with company's small business status"
    ],
    "requirements": [
      "Active SAM.gov registration",
      "Furnish all labor, supplies, and equipment"
    ],
    "risks": [
      "Performance Work Statement is only a draft attachment (final requirements could differ)",
      "Oklahoma-based firm must be able to service the New Mexico location"
    ],
    "evidenceQuote": null,
    "evidenceVerified": false,
    "suggestedNextStep": "Confirm ability to perform work at the listed location and read the full notice on SAM.gov."
  },
  "aiModel": "grok-4.20-0309-non-reasoning",
  "aiDisclaimer": "Automated screening by Grok (xAI). Not procurement or legal advice. Check the full solicitation and attachments on SAM.gov before bidding.",
  "aiStatus": "scored",
  "descriptionChars": 3409,
  "descriptionSnippet": "BGNDRF CUSTODIAL SERVICES PRESOLICITATION NOTICE: ... (1) Point of contact: [contact removed], at [contact removed] (2) Set asides: Total Small Business Set-aside (100%) ..."
}
```

Here `evidenceQuote` is null and `evidenceVerified` is false because the model's quote did not match the notice text word for word, so it was dropped. `placeOfPerformance` fields are null when the notice leaves them blank.

Rows that are not sent to Grok have no `aiFit` object and `aiStatus` such as `not_requested` or `over_ai_limit`. A run summary is stored in the key-value store under `SUMMARY`.

The Opportunities table shows title, agency, type, set-aside, NAICS, deadline, days left, match score, AI fit, AI verdict, AI summary, and the SAM.gov link.

### Pricing

Pay per event:

| Event | Price | When it is charged |
| --- | --- | --- |
| Run started | $0.01 | Once per run |
| Opportunity returned | $0.003 | Once per matching opportunity written to the dataset |
| AI bid-fit score | $0.04 | Once per opportunity scored with the Actor's xAI key |

The AI bid-fit score event is not charged when you supply your own xAI key. You still pay xAI for those calls.

Example: 10 opportunities and 2 AI scores, using the Actor's key, is $0.01 + $0.03 + $0.08 = $0.12. The same run with your own key is $0.04, plus whatever xAI charges you. You can set a maximum charge for the run in Apify. The Actor stops when that limit is reached.

These event prices are the listing prices. They are not the same as Apify platform usage, which is separate unless the owner turns on "Pay per event + usage".

### Contact details

The public extract includes contracting officers' names, email addresses, and phone numbers. This Actor does not copy those fields into the output. Before a title, description, or other free-text field is saved or sent to Grok, emails (including `name [at] agency [dot] gov` and `(at)` forms), phone numbers, and the name, email, and phone listed on that notice are removed and replaced with `[contact removed]`.

A name that is written in the notice but is not in that notice's contact fields can still appear. Use the `samUrl` link and get official contact information on SAM.gov.

### Disclaimer

This Actor does not guarantee that you will win any contract, that a notice is still open, or that the AI score is correct. It is not affiliated with, endorsed by, or operated by SAM.gov or GSA. Confirm every opportunity on SAM.gov before you bid.

# Actor input Schema

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

Whole-word, case-insensitive match in the notice title or description. An opportunity is kept if any keyword matches. Leave empty to match on the other filters only.

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

Keep notices whose NAICS code starts with any of these (e.g. 5617 matches 561720 and 561730).

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

Keep only notices with one of these set-aside codes. Add NONE to also keep unrestricted (full and open) notices. Leave empty for all.

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

Which SAM.gov notice types to include. Default: biddable and early-stage notices.

## `placeOfPerformanceStates` (type: `array`):

Two-letter US state codes (e.g. TX, VA). Leave empty for anywhere.

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

Keep notices whose department, sub-tier, or office contains any of these words (e.g. VETERANS, ARMY, FOREST SERVICE).

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

Only notices posted in the last N days.

## `minDaysUntilDeadline` (type: `integer`):

Drop notices whose response deadline is closer than this. Notices with no deadline are dropped when this is set.

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

Best matches first. Each returned opportunity is one billable result.

## `includeFullDescription` (type: `boolean`):

Off: a 600-character snippet. On: the full notice text, with contact details removed.

## `aiFitScoring` (type: `boolean`):

Have Grok read each top notice against your company profile and return a 0-100 fit score, reasons, key requirements, risks, and a quote from the notice (checked word-for-word). Needs a company profile.

## `companyProfile` (type: `string`):

What you do, certifications and set-aside status, past performance, team size, locations you can serve, and contract sizes you can handle. Minimum 40 characters.

## `maxAiScored` (type: `integer`):

Only the top N results (by match score) are sent to Grok.

## `maxGrokSpendUsd` (type: `number`):

Hard cap on Grok API cost per run. Calls stop once the next call could cross it. The Actor also enforces its own ceiling.

## `grokModel` (type: `string`):

Model used for fit scoring.

## `xaiApiKey` (type: `string`):

Use your own xAI key. Grok costs then go to your xAI account and the AI fit-score event is not charged.

## Actor input object example

```json
{
  "keywords": [
    "janitorial",
    "custodial"
  ],
  "naicsCodes": [
    "561720"
  ],
  "noticeTypes": [
    "Solicitation",
    "Combined Synopsis/Solicitation",
    "Presolicitation",
    "Sources Sought"
  ],
  "postedWithinDays": 30,
  "minDaysUntilDeadline": 3,
  "maxResults": 100,
  "includeFullDescription": false,
  "aiFitScoring": true,
  "companyProfile": "Small commercial cleaning company in Texas, 40 staff, SBA-certified small business. Janitorial, floor care, and window cleaning for offices and clinics up to 200,000 sq ft. Past performance: two county government buildings and a VA outpatient clinic. Can serve TX, OK, and LA.",
  "maxAiScored": 25,
  "maxGrokSpendUsd": 0.5,
  "grokModel": "grok-4.20-0309-non-reasoning"
}
```

# Actor output Schema

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

Matching opportunities ranked by match score (overview table view).

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

Every field of each matching opportunity.

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

Counts, AI status, key source, and estimated Grok spend for the run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "keywords": [
        "janitorial",
        "custodial"
    ],
    "naicsCodes": [
        "561720"
    ],
    "companyProfile": "Small commercial cleaning company in Texas, 40 staff, SBA-certified small business. Janitorial, floor care, and window cleaning for offices and clinics up to 200,000 sq ft. Past performance: two county government buildings and a VA outpatient clinic. Can serve TX, OK, and LA."
};

// Run the Actor and wait for it to finish
const run = await client.actor("edge_works/sam-gov-bid-fit-finder").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": [
        "janitorial",
        "custodial",
    ],
    "naicsCodes": ["561720"],
    "companyProfile": "Small commercial cleaning company in Texas, 40 staff, SBA-certified small business. Janitorial, floor care, and window cleaning for offices and clinics up to 200,000 sq ft. Past performance: two county government buildings and a VA outpatient clinic. Can serve TX, OK, and LA.",
}

# Run the Actor and wait for it to finish
run = client.actor("edge_works/sam-gov-bid-fit-finder").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": [
    "janitorial",
    "custodial"
  ],
  "naicsCodes": [
    "561720"
  ],
  "companyProfile": "Small commercial cleaning company in Texas, 40 staff, SBA-certified small business. Janitorial, floor care, and window cleaning for offices and clinics up to 200,000 sq ft. Past performance: two county government buildings and a VA outpatient clinic. Can serve TX, OK, and LA."
}' |
apify call edge_works/sam-gov-bid-fit-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,edge_works/sam-gov-bid-fit-finder"
        }
    }
}
```

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/rirRdBzUyJHCCHcnM/builds/duDW8RixBRgVCDmLd/openapi.json
