# Gov Bids, RFPs & Federal Awards API (SAM.gov) (`civic-data-tools/public-bid-search`) Actor

Open government bids, RFPs and new federal contract awards from official APIs only: USAspending, SAM.gov (your own key), NYC City Record, RAMP LA, Delaware, Montgomery County MD. One normalized schema.

- **URL**: https://apify.com/civic-data-tools/public-bid-search.md
- **Developed by:** [Civic Data Tools](https://apify.com/civic-data-tools) (community)
- **Categories:** AI, Automation, Developer tools
- **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

## Public Bid & RFP Search (Gov Contracts API)

One call returns **open government solicitations** (bids, RFPs, RFQs) and **new federal contract awards** in a single normalized schema. Every source is an official government API or open-data portal (see `sources.md` for the terms review). No scraping, no logins, no commercial aggregators.

### Sources (v0.1)

| id | Coverage | Kind |
|---|---|---|
| `usaspending-awards` | All federal agencies: new contract awards in the last N days (who won what, and for how much) | award |
| `sam-gov-opportunities` | Federal open opportunities. **Only with your own SAM.gov API key** (`samApiKey`) | open solicitation |
| `nyc-city-record` | New York City agencies (City Record Online solicitations) | open solicitation |
| `la-ramp` | Los Angeles region: City of LA, LA County, LAUSD, ports, airports (RAMP LA) | open solicitation |
| `delaware-open-bids` | State of Delaware agencies | open solicitation |
| `montgomery-county-md` | Montgomery County, Maryland | open solicitation |

### Input

`keywords` (whole word, simple plurals; `landscap*` for prefix), `matchMode`, `sources`, `states`, `naicsCodes` (prefix match; federal only), `onlyOpen` (default true), `postedWithinDays` (60), `includeFederalAwards` (true), `awardsWithinDays` (30), `samApiKey` (secret, optional), `samMaxRequests` (1–10, default 1), `socrataAppToken` (secret, optional), `maxResultsPerSource` (200), `maxResults` (500). Every field is documented in `.actor/input_schema.json`.

### SAM.gov (optional, bring your own key)

- Paste your free personal SAM.gov key into `samApiKey` (a secret field: Apify stores it encrypted). Setup steps: `SAM-GOV-SETUP.md`.
- Personal keys allow **10 requests/day** (1,000 if your SAM.gov account holds an entity role) and **expire every 90 days**. The Actor never retries SAM.gov and spends at most `samMaxRequests` calls per run (default 1).
- With keywords and `keywords ≤ samMaxRequests`, each keyword becomes one server-side SAM.gov `title` search. Otherwise the newest 1,000 notices per request are scanned and matched locally. `OUTPUT.stats.perSource["sam-gov-opportunities"].meta` reports `strategy`, `requestsUsed`, `totalAvailable`, `fetched`, and `partial` (with a hint).
- Errors are explicit: `AUTH_ERROR` (bad, expired, or wrongly formatted key, with the renewal steps), `RATE_LIMITED` (daily quota used). A SAM.gov "No Data found" 404 counts as an empty result. The key is redacted from every log line and the OUTPUT record.

### Output record (`type: "bid-record"`)

`source, sourceName, recordKind (open-solicitation | award), level (federal | state | county | city | regional), jurisdiction, state, agency, subAgency, title, description, solicitationNumber, noticeType, category, naics{code,description}, psc, postedDate, dueDate, value, valueType, recipient (awards), periodEnd (awards), contact (NYC), link, matchedKeywords, disclaimer (Montgomery), retrievedAt`. See `demo/actor-sample-item.json`.

Errors never crash the run. They are written to the `OUTPUT` record as `AUTH_ERROR`, `RATE_LIMITED`, or `SOURCE_ERROR`. SAM.gov without a key appears under `skipped: NO_API_KEY`.

### Pricing (pay per event)

- `bid-record` (primary): one per record written to the dataset. Proposed price **$0.005** (set in Apify Console, not in code).
- Keep `apify-actor-start` (synthetic). **Remove `apify-default-dataset-item`** so records aren't charged twice.
- The Actor follows the user's spending limit (`eventChargeLimitReached` stops the run cleanly). Events only (no "+ usage"), limited permissions, no Standby, so it's eligible for Apify agentic payments once KYC is done.

### Run

```bash
npm install
npm test                 # 10 offline tests (all 6 sources mocked, including SAM.gov strategy/quota/key handling)
LIVE=1 npm test          # plus a live test against the 5 keyless sources
npm run demo             # every open record → demo/demo-all-open.json
npm start                # Actor locally (input in storage/key_value_stores/default/INPUT.json)
read -rs SAM_API_KEY && export SAM_API_KEY                # paste key, nothing is echoed
node scripts/sam-smoke.mjs landscaping                   # 1 SAM.gov request, prints a redacted summary
node src/cli.js --keywords landscaping --sam-requests 1  # all sources incl. SAM.gov
```

### Demo results (live, 2026-10-04 PT)

- All open records, no keywords: **422 records in about 7 s, 0 errors**. That's 100 new federal awards, 91 NYC, 186 RAMP LA, 36 Delaware, and 9 Montgomery County.
- Grounds-maintenance niche (`landscaping, grounds maintenance, mowing, tree(s), vegetation, irrigation`): 39 records. NYC Parks "LiDAR Based Street Tree Inventory" (due 2026-10-20), LADWP "Drip Tubing for Owens Lake Managed Vegetation" (due 2026-10-22), and 37 new federal awards. Examples: VA "Grounds Maintenance Services… Santa Fe National Cemetery" ($451,595, Moderno Inc), USDA "ALB Tree Removal Services in South Carolina" ($6.72M, Davey Resource Group).
- Local Actor run (Apify SDK), `janitorial, custodial, cleaning`: 36 records, `bid-record` charged 36 times (simulated).

### Notes and limits

- USAspending gives awards, not open bids. They're useful for competitive intel and recompete planning. `dueDate` is null for awards.
- USAspending keyword search runs server-side across many fields, then our whole-word filter runs on title, description, and NAICS title. Broad prefixes like `landscap*` also match NAICS 541320 "Landscape Architectural Services".
- Montgomery County records have no per-item URL, so `link` goes to the County solicitations page.
- **Disclaimers.** Montgomery County: "The data made available here has been modified for use from its original source, which is Montgomery County, Maryland ('the County'). THE COUNTY MAKES NO REPRESENTATIONS OR WARRANTIES AS TO THE COMPLETENESS, ACCURACY, OR TIMELINESS OF ANY DATA." NYC: "The City of New York can not vouch for the accuracy or completeness of data provided by this web site or application or for the usefulness or integrity of the web site or application. This site provides applications using data that has been modified for use from its original source, NYC.gov, the official web site of the City of New York." LA data: City of Los Angeles, CC0. Not affiliated with any government.

### Civic Data Tools (related Actors)

Same publisher (`civic-data-tools`) — useful together for agents monitoring local government:

| Actor | Use when | Store |
|---|---|---|
| **City Council & County Agenda Search** | Keyword/topic matches in council, board, and commission agendas (Legistar / CivicClerk / Granicus) | [meeting-agenda-search](https://apify.com/civic-data-tools/meeting-agenda-search) |
| **Multi-City Building Permits** | Normalized building permits from large US city open-data portals | [building-permits](https://apify.com/civic-data-tools/building-permits) |

This Actor covers **open bids / RFPs / federal awards**. Pair agenda search for “who is about to bid out work” and permits for “what already pulled a permit.”

# Actor input Schema

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

Optional. Case-insensitive whole-word match on title, description, category (NAICS title for federal awards) and agency (simple plurals match; end a keyword with \* for prefix match, e.g. "landscap\*"). Leave empty to return everything open. Example: "landscaping", "janitorial".

## `matchMode` (type: `string`):

"any" keeps records matching at least one keyword; "all" requires every keyword.

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

Leave empty for all sources. SAM.gov is used only when you supply your own samApiKey.

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

Optional two-letter codes (e.g. CA, NY). Limits local sources to those states and federal records to that place of performance.

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

Optional 2–6 digit NAICS codes (e.g. 561730 landscaping). Applied to federal sources; local sources have no NAICS and are excluded when this is set.

## `onlyOpen` (type: `boolean`):

Keep only solicitations whose due date is today or later. Does not affect federal award records.

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

Only solicitations first posted in the last N days.

## `includeFederalAwards` (type: `boolean`):

Include newly awarded federal contracts from USAspending (who won what) alongside open solicitations.

## `awardsWithinDays` (type: `integer`):

Look-back window for new USAspending awards.

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

Your own free SAM.gov public API key (looks like SAM-xxxxxxxx-xxxx-…). Get it at sam.gov → sign in → Profile → Account Details → Public API Key. Enables federal open opportunities. Stored encrypted. Personal keys allow 10 requests/day (1,000 if your SAM.gov account has an entity role) and expire every 90 days – paste the new key when SAM.gov emails you.

## `samMaxRequests` (type: `integer`):

How many SAM.gov API calls this run may spend from your daily quota (personal keys: 10/day). With keywords, each keyword becomes one server-side title search when keywords ≤ this number; otherwise the newest 1,000 notices per request are scanned and matched locally. OUTPUT shows coverage.

## `socrataAppToken` (type: `string`):

Optional free app token for open-data portals (NYC, LA, Delaware, Montgomery) to avoid shared rate limits.

## `maxResultsPerSource` (type: `integer`):

Cap on records kept from each source.

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

Hard cap on records (and charges) per run.

## Actor input object example

```json
{
  "keywords": [
    "landscaping",
    "tree"
  ],
  "matchMode": "any",
  "onlyOpen": true,
  "postedWithinDays": 60,
  "includeFederalAwards": true,
  "awardsWithinDays": 30,
  "samMaxRequests": 1,
  "maxResultsPerSource": 200,
  "maxResults": 500
}
```

# Actor output Schema

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

Dataset of bid and award records matching your filters.

# 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": [
        "landscaping",
        "tree"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("civic-data-tools/public-bid-search").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": [
        "landscaping",
        "tree",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("civic-data-tools/public-bid-search").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": [
    "landscaping",
    "tree"
  ]
}' |
apify call civic-data-tools/public-bid-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,civic-data-tools/public-bid-search"
        }
    }
}
```

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/7xN3waBaF0PpKEMfP/builds/Gg6deWdgTDLVzciw2/openapi.json
