# US Multi-state Public Bid Subscription Monitor (`slate_spool/us-multistate-bid-monitor`) Actor

Scheduled new-or-changed bid and RFP alerts from official New York City, Montgomery County (MD), and District of Columbia public procurement feeds.

Related Actor: [LaPAC bids](https://apify.com/slate_spool/lapac-bid-monitor)

- **URL**: https://apify.com/slate\_spool/us-multistate-bid-monitor.md
- **Developed by:** [Wes Shields](https://apify.com/slate_spool) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $49,000.00 / 1,000 30-day bid monitor access passes

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

## US Multi-state Public Bid Subscription Monitor

A staged, credential-free Apify Actor for scheduled monitoring of official public procurement feeds in New York City, Montgomery County (Maryland), and the District of Columbia. It persists a named watch across runs and emits **only new or changed matching bids**, not a recurring dump.

This build is local staging only. It has not been published, deployed, scheduled, or connected to billing.

### Subscription workflow

1. Set a stable `monitorId` and choose regions, category fragments, and keywords once.
2. Run the Actor once to establish the baseline. `emitInitialMatches` defaults to `false`, so existing records are not dumped.
3. Create an Apify schedule from `staging/schedule-template.json` after the manual publish gate.
4. Later cycles compare normalized records with `MONITOR_STATE` in the named KV store `us-multistate-bid-monitor-<monitorId>`.
5. Only new or changed records related to the saved filter are pushed. A record leaving the filter emits an explicit `exited` change.

Saved filter changes fail visibly for an existing `monitorId`; create a new ID for a different subscription. The webhook URL is used for delivery but is never persisted or logged.

### Alerts

Every emitted dataset event contains:

- `changeType`, `matchTransition`, and exact `changedFields`
- current and previous normalized records
- `emailReady.subject` and `emailReady.textBody`
- canonical official source URL

When `webhookUrl` is supplied, the Actor makes one HTTPS JSON POST per event. A failed delivery produces a non-zero run, marks `silentFailure: false`, and does not advance monitor state. Delivery is therefore at least once; receivers should deduplicate by `monitorId`, source/id, and `detectedAt`.

### PAY\_PER\_EVENT staging

`staging/pricing-pay-per-event.json` defines the primary `monitor-access-30-days` event at **$49** and Apify's synthetic `apify-actor-start` event at **$0.00005**. Default-dataset item charging is explicitly disabled, so per-record pricing is not the primary model. The Actor calls `Actor.charge()` only when a named monitor has no active 30-day pass. Apify Console monetization remains a Wes/Jeff manual action.

### Safety and failure contract

- Hard-coded HTTPS government source hosts only; no credentials, cookies, proxies, or private sources.
- Sequential requests with a minimum 1,000 ms delay, bounded retries, 20-request maximum, 200-record maximum, 120-second maximum, and 512 MB Actor memory maximum.
- All selected sources must complete and normalize successfully before state can advance.
- State refuses to silently prune above its configured hard cap.
- No source failure, webhook failure, state mismatch, or budget failure is returned as success.

### Local verification

```sh
npm install
npm test
npm run validate:diff
npm run validate
```

The independent known-change answer key runs three watch cycles. Cycle 1 establishes a baseline; cycles 2 and 3 contain manually labeled new and changed records. `validation/diff-verification.json` is the reproducible score artifact and fails unless field accuracy is at least 0.95 with zero missed changes and zero silent failures.

The live field-normalization check uses independent official CSV/detail surfaces and may fail visibly if a government endpoint is temporarily unavailable. No failure is converted to an empty success.

### Related Actor

- [LAPAC Bid Monitor](https://apify.com/slate_spool/lapac-bid-monitor) — Continue your workflow with this related data source.

# Actor input Schema

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

Stable lowercase ID used for the named cross-run state store. Use a new ID for different filters.

## `regions` (type: `array`):

US regions to watch; mapped to the currently supported official portals.

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

Case-insensitive terms matched against title, agency, notice type, category, and description.

## `keywordMode` (type: `string`):

Choose whether a bid must match any configured keyword or every configured keyword.

## `categories` (type: `array`):

Optional case-insensitive category fragments.

## `webhookUrl` (type: `string`):

Optional HTTPS endpoint without embedded credentials or query secrets. One JSON POST is sent per new or changed match. The URL is never persisted or logged by monitor state/output.

## `emitInitialMatches` (type: `boolean`):

Off by default so the first scheduled cycle establishes state without dumping existing records.

## `includeClosed` (type: `boolean`):

Include closed notices in each source scan instead of limiting collection to open notices.

## `maxRecords` (type: `integer`):

Hard cap on the total records collected in one watch cycle.

## `maxRecordsPerSource` (type: `integer`):

Hard cap on records collected from any one official source in a watch cycle.

## `stateRecordLimit` (type: `integer`):

Fails visibly instead of silently pruning when reached.

## `maxRequests` (type: `integer`):

Hard cap on official-source HTTP requests, including retries, in one watch cycle.

## `requestDelayMs` (type: `integer`):

Minimum polite delay in milliseconds between official-source HTTP requests.

## `timeoutSecs` (type: `integer`):

Hard wall-clock collection timeout for one watch cycle.

## Actor input object example

```json
{
  "monitorId": "my-bid-watch",
  "regions": [
    "NY",
    "MD",
    "DC"
  ],
  "keywords": [],
  "keywordMode": "any",
  "categories": [],
  "emitInitialMatches": false,
  "includeClosed": false,
  "maxRecords": 60,
  "maxRecordsPerSource": 25,
  "stateRecordLimit": 2000,
  "maxRequests": 12,
  "requestDelayMs": 1100,
  "timeoutSecs": 90
}
```

# Actor output Schema

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

No description

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

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("slate_spool/us-multistate-bid-monitor").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("slate_spool/us-multistate-bid-monitor").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 '{}' |
apify call slate_spool/us-multistate-bid-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,slate_spool/us-multistate-bid-monitor"
        }
    }
}
```

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/EvvjTF8n4Ce9qG5lz/builds/IKOKLAncAjRNvXF9D/openapi.json
