# BidDelta — SAM.gov Material Change Monitor (`optimus-connections/bid-delta`) Actor

Free beta for capture and proposal teams: BidDelta compares saved SAM.gov opportunity snapshots and flags deadline, status, contact, award, and attachment-metadata changes with source-linked before-and-after evidence. No BidDelta fee; Apify usage applies. Contact: contact@optimusconnections.net

- **URL**: https://apify.com/optimus-connections/bid-delta.md
- **Developed by:** [Jeremy DeCoste](https://apify.com/optimus-connections) (community)
- **Categories:** Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## BidDelta — SAM.gov material change watchlist

**BidDelta tells you what changed in a federal contract opportunity you already
care about.** It watches narrow searches or known notices through the official
SAM.gov Contract Opportunities Public API and produces source-linked alerts
when pursuit-relevant fields change.

This release is a limited free-beta candidate. There is no BidDelta fee during
the beta; users pay only the normal Apify platform usage consumed by their own
runs. The beta exists to prove reliability and usefulness before any future
monetization decision.

### What BidDelta watches

- response deadlines;
- official active status;
- set-aside code and description;
- notice type and solicitation number;
- NAICS and classification codes;
- place of performance and point of contact;
- award metadata;
- official description and attachment-link metadata;
- new opportunities matching a configured search.

Each result includes before-and-after values, a review priority, why the change
may matter, a deterministic event ID, matching search IDs, an official notice
link, source attribution, retrieval time, and explicit coverage boundaries.

BidDelta does not make a bid/no-bid decision. Always verify the current official
notice before changing a pursuit or submission plan.

### Try it without a SAM.gov key

Fixture mode makes no network request. Use the prepared
`examples/fixture-demo-input.json`, or keep:

```json
{
  "monitorId": "fixture-demo",
  "dryRun": true,
  "sourceMode": "fixture",
  "emitBaseline": true,
  "searches": [
    {
      "id": "example-hvac",
      "lookbackDays": 90,
      "naicsCode": "238220",
      "limit": 100
    }
  ]
}
```

The fixture demonstration emits a clearly synthetic example. For a real
monitor, keep `emitBaseline=false`; the first successful run then establishes a
free baseline rather than reporting existing notices as changes.

### Run against official SAM.gov data

Live source mode requires your own SAM.gov public API key. Enter it in the
masked **SAM.gov public API key** field. Apify encrypts secret inputs before
storing them and decrypts them only inside the Actor run.

Private deployments may alternatively provide the secret environment variable
`SAM_GOV_API_KEY`. Never place a key in an ordinary field, source code, logs,
datasets, or a shared URL.

Start with a deliberately narrow dry run:

```json
{
  "monitorId": "my-bid-watch",
  "dryRun": true,
  "sourceMode": "sam-api",
  "emitBaseline": false,
  "searches": [
    {
      "id": "hvac-alabama",
      "lookbackDays": 7,
      "naicsCode": "238220",
      "state": "AL",
      "limit": 10
    }
  ],
  "maxPagesPerSearch": 1,
  "missingThreshold": 2
}
```

For unattended monitoring, use `lookbackDays`; BidDelta recalculates the date
window on every run. For a known older opportunity, use an exact `noticeId` or
`solicitationNumber` with fixed `postedFrom` and `postedTo` dates.

### Safety and coverage boundaries

- Uses the official SAM.gov public API and supported user credentials.
- Never bypasses source access controls.
- Strips query strings before persisting source links, reducing API-key leak
  risk.
- Treats attachments as metadata only; it never downloads or redistributes
  attachment content.
- Fails without advancing monitor state when a result set is truncated.
- Requires repeated successful misses before reporting a missing-record signal.
- Never labels absence alone as a cancellation.
- Compares BidDelta's stored snapshots because the public API returns the latest
  active version, not complete source version history.
- Requires official-source verification before a real pursuit decision.

BidDelta is maintained independently and is not affiliated with or endorsed by
SAM.gov or the U.S. General Services Administration.

### Output and duplicate handling

Dataset delivery is at-least-once. Every material event has a deterministic
`eventId`, so downstream systems can suppress duplicate projections.

During the free beta, keep `dryRun=true`; no custom Pay-Per-Event charge is
attempted. The future billing path:

1. saves the event to a durable outbox;
2. confirms the run budget before exposing a paid result;
3. records the current run's authoritative charged-event count;
4. projects the customer-visible result;
5. saves a charge intent and deterministic idempotency key;
6. performs bounded same-key retries, including safe same-run restart recovery;
7. reconciles a prior terminal run before any cross-run retry;
8. stops later billing whenever the authoritative count is unavailable or
   inconsistent.

Under the single-monitor, non-overlapping-run boundary, a later run retries
only when the prior terminal run proves no charge was accepted. Strict global
exactly-once billing is not claimed when that boundary is violated or the
authoritative run record cannot be read.

### Recommended beta operation

- 128 MB memory;
- one narrow monitor per stable `monitorId`;
- `dryRun=true`;
- no overlapping runs for the same monitor;
- no schedule until two reviewed API-mode runs succeed;
- daily or hourly checks only after measuring real source freshness and cost.

### Feedback that matters

Open the Actor's **Issues** tab, include the event's `eventId`, and answer:

1. What worked?
2. What was confusing or wrong?
3. Did this alert change or protect an action?

The beta measures unique and repeat users, source-verified material changes,
false alerts, source failures, retained monitors, run cost, and support time.
Raw run count alone is not proof of demand.

### Local verification

```bash
npm ci
npm test
npm run check
npx apify-cli validate-schema
```

The automated suite covers normalization, baseline behavior, material deltas,
missing-record semantics, source-identity resets, pagination, truncation,
credential sanitization, budget refusal, durable outbox ordering, same-run
idempotent charge retry, terminal-run charge reconciliation, proof-based
cross-run retry, and fail-closed billing uncertainty.

### Documentation

- `docs/PRODUCT_REVERSE_SPEC.md`
- `docs/STATE_AND_BILLING_PROTOCOL.md`
- `docs/LIVE_VALIDATION_RUNBOOK.md`
- `docs/PRIVATE_PILOT.md`
- `docs/FREE_BETA_RELEASE.md`
- `docs/BETA_METRICS.md`
- `docs/COST_AND_PRICING_MODEL.md`
- `docs/PUBLISHING_DRAFT.md`
- `docs/VERIFICATION_REPORT.md`

# Actor input Schema

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

Stable identifier used for persistent state. Changing it creates a new baseline.

## `dryRun` (type: `boolean`):

Preview events without Pay-Per-Event charging. Keep enabled until a baseline and repeat run have been reviewed.

## `sourceMode` (type: `string`):

Try the safe sample first, then select the official SAM.gov API for real monitoring.

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

Your own SAM.gov public API key. Apify encrypts this value at rest and decrypts it only inside your Actor run. A private deployment may alternatively provide SAM\_GOV\_API\_KEY as a secret environment variable.

## `searches` (type: `array`):

Each profile becomes one official API query. At least one narrowing field is required per profile.

## `emitBaseline` (type: `boolean`):

Demonstration-only. Official API mode always establishes its first successful baseline silently and without a material-event charge.

## `fixtureData` (type: `array`):

Optional SAM-style records for no-network testing.

## `maxPagesPerSearch` (type: `integer`):

Safety cap for pagination. If the result set exceeds this cap, the run fails closed and does not advance monitor state.

## `missingThreshold` (type: `integer`):

How many consecutive successful queries must miss a previously matched notice before emitting a non-cancellation missing signal.

## Actor input object example

```json
{
  "monitorId": "fixture-demo",
  "dryRun": true,
  "sourceMode": "fixture",
  "searches": [
    {
      "id": "example-hvac",
      "lookbackDays": 90,
      "naicsCode": "238220",
      "limit": 100
    }
  ],
  "emitBaseline": true,
  "fixtureData": [
    {
      "noticeId": "11111111-1111-1111-1111-111111111111",
      "title": "Sample HVAC Preventive Maintenance",
      "solicitationNumber": "SAMPLE-HVAC-001",
      "postedDate": "2026-08-20",
      "responseDeadLine": "2026-09-22T17:00:00-05:00",
      "active": "Yes",
      "type": "Solicitation",
      "naicsCode": "238220",
      "uiLink": "https://sam.gov/"
    }
  ],
  "maxPagesPerSearch": 10,
  "missingThreshold": 2
}
```

# Actor output Schema

## `materialChanges` (type: `string`):

Source-linked events written to the default dataset.

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

Counts, resolved coverage, outbox state, and completion status.

# 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 = {
    "monitorId": "fixture-demo",
    "searches": [
        {
            "id": "example-hvac",
            "lookbackDays": 90,
            "naicsCode": "238220",
            "limit": 100
        }
    ],
    "fixtureData": [
        {
            "noticeId": "11111111-1111-1111-1111-111111111111",
            "title": "Sample HVAC Preventive Maintenance",
            "solicitationNumber": "SAMPLE-HVAC-001",
            "postedDate": "2026-08-20",
            "responseDeadLine": "2026-09-22T17:00:00-05:00",
            "active": "Yes",
            "type": "Solicitation",
            "naicsCode": "238220",
            "uiLink": "https://sam.gov/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("optimus-connections/bid-delta").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 = {
    "monitorId": "fixture-demo",
    "searches": [{
            "id": "example-hvac",
            "lookbackDays": 90,
            "naicsCode": "238220",
            "limit": 100,
        }],
    "fixtureData": [{
            "noticeId": "11111111-1111-1111-1111-111111111111",
            "title": "Sample HVAC Preventive Maintenance",
            "solicitationNumber": "SAMPLE-HVAC-001",
            "postedDate": "2026-08-20",
            "responseDeadLine": "2026-09-22T17:00:00-05:00",
            "active": "Yes",
            "type": "Solicitation",
            "naicsCode": "238220",
            "uiLink": "https://sam.gov/",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("optimus-connections/bid-delta").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 '{
  "monitorId": "fixture-demo",
  "searches": [
    {
      "id": "example-hvac",
      "lookbackDays": 90,
      "naicsCode": "238220",
      "limit": 100
    }
  ],
  "fixtureData": [
    {
      "noticeId": "11111111-1111-1111-1111-111111111111",
      "title": "Sample HVAC Preventive Maintenance",
      "solicitationNumber": "SAMPLE-HVAC-001",
      "postedDate": "2026-08-20",
      "responseDeadLine": "2026-09-22T17:00:00-05:00",
      "active": "Yes",
      "type": "Solicitation",
      "naicsCode": "238220",
      "uiLink": "https://sam.gov/"
    }
  ]
}' |
apify call optimus-connections/bid-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,optimus-connections/bid-delta"
        }
    }
}
```

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/zY0h0JBr5bv42AG39/builds/CKW8P9ttH8JbVWR3m/openapi.json
