# E-rate Contract Evidence & Extension Monitor (`cauldo/erate-contract-evidence`) Actor

USAC Form 471 contract snapshots by applicant BEN and contract ID, reported expiration and extension evidence, exact historical Form 470 joins, and changes between complete observations.

- **URL**: https://apify.com/cauldo/erate-contract-evidence.md
- **Developed by:** [Cauldo](https://apify.com/cauldo) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 completed contract snapshots

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

## E-rate Contract Evidence & Extension Monitor

Turn official USAC Form 471 evidence into one record per exact applicant BEN + USAC contract ID. Compare reported expiration dates, extension terms and funding-status edits across complete observations, with CSV exports and underlying FRN provenance. Use it for bounded E-rate contract research and recurring evidence checks.

Start with one applicant BEN or a narrow state/provider scope. A first complete observation seeds the monitor; later observations return new contracts and semantic changes. An unchanged observation still produces a complete contract export.

Reported dates are **not confirmed renewal or rebid deadlines**. The extended date is not asserted to be exercised, maximum, or later than the contract end. Historical FRN provider SPIN/name is not asserted to be the current incumbent. No contract lifetime value is computed from annual FRNs.

### Bounded example

The Store trial prefill uses one verified BEN (`17027710`), empty state filters, at most five contracts and 1,000 rows. Clear that sample BEN when switching to a state/provider-only input. API calls with no BEN/SPIN filter default to NY; supply an explicit bounded scope for a first trial.

```json
{
  "bens": ["17027710", "130359", "132748"],
  "fundingYears": [2024, 2025, 2026],
  "monitorName": "contract-evidence-test",
  "maxContracts": 10,
  "maxRawRows": 1000,
  "maxRunSecs": 180
}
```

Omit states when using BEN/provider filters. Without BENs/SPINs, the default is one state (`NY`). At most one state, 100 BENs and 100 nine-digit provider SPINs are accepted; dimensions combine as AND, selections within one dimension as OR. Funding years are explicit or default to the current UTC year and previous two, at most ten years. Optional `reportedEndFrom`/`reportedEndTo` bound **reported** end dates inclusively. Keep discovery scope/date bounds fixed per monitor; funding years may roll forward.

Discovery requires Current + Funded + Contract and a Category 1 service (Data Transmission and/or Internet Access or historical Voice). It excludes Original, denied/cancelled, tariff/month-to-month and Category 2. Missing contract IDs remain unresolved; matching by name, provider or contract number is prohibited. Selected contracts' exact history is fetched across the chosen years without discovery date/provider/status restrictions. Previously watched identities stay watched even when dates leave the window or funding status changes.

### Evidence and changes

- One exact contract record contains its underlying Current FRNs, annual statuses, latest funding year, applicant/state, historical provider name/SPIN, reported/extended dates, extension wording/counts, source URLs and snapshot/retrieval times. Original rows are counted/excluded, and exact duplicate copies are deduplicated.
- Conflicting latest-year dates remain arrays; the single date is null when no unique value exists. Invalid dates/counts stay unknown with flags. Raw date strings remain in FRN evidence. The real 2030-06-30 vs 2025-06-30 and 2028-01-31 vs 2028-01-30 examples remain separate and flagged.
- A historical Form 470 joins only when `establishing_form_470 == application_number` **and** BEN equals `billed_entity_number`. Original historical forms are explicitly labeled. No new Form 470 is inferred to replace a contract.
- First complete read seeds history. Later complete reads emit newly observed contracts and exact semantic date/extension/provider/funding-status changes. New annual FRNs, funding-year rollover and elapsed time alone do not emit changes. Existing FRN status edits are compared even in older years. A denied FRN is not labeled a cancelled contract.
- Missing FRNs/contracts retain last-known-good evidence with `observedThisRead: false`, original retrieval time and a carry-forward flag. They do not create a disappearance/cancellation event. `currentFrnCount` includes retained last-known-good evidence; `observedCurrentFrnCount` counts rows seen in this read. Review coverage and flags before using stale evidence.

### Completeness and persistent monitoring

Anonymous [USAC SODA2 FRN data](https://opendata.usac.org/resource/qdmp-ygft.json) and [historical Form 470 data](https://opendata.usac.org/resource/jt8s-3q52.json) supply projected fields. Their schema metadata/`rowsUpdatedAt` is read before and after all pages/joins. Missing metadata, schema changes, refresh races, repeated pages, source errors and caps produce `complete: false` and preserve the baseline. Snapshot publication is separate from extraction.

Hard maximums: **1,000 watched/discovered contracts, 50,000 raw rows across all source reads, 10 MB per response and 10 MB snapshot, 60 seconds/request, two concurrent HTTP calls**, and three attempts with capped 429/503 backoff. Default run source budget is 120 seconds, maximum 180; the cloud runtime additionally reserves 15 seconds for final output. A full page exactly at the raw-row cap is treated as incomplete because exhaustion was not proven. Limits do not silently publish a truncated baseline.

Cloud history uses a named KVS in the run account, or an empty dedicated `historyStoreId` selected with READ/WRITE grants. API metadata must identify the current run account as its owner. Shared or foreign-account stores and stores with unrelated keys are rejected; existing-user ownership is tested, while a second customer account has not been used. A dedicated RequestQueue singleton is locked server-side using a run-specific client key. A resurrected run reclaims its own unexpired lease by server-verified renewal; a different run cannot renew that lease; the lock is checked/renewed before source requests and publication. KVS reads are bounded to 20 seconds with one retry; record writes use a 15-second timeout without timeout retries. The lease is renewed immediately before final pointer reads/writes and must have at least 90 seconds remaining. A separate default-run-store recovery marker prevents a pruned prepared journal from being silently rebuilt. The immutable content-addressed snapshot is verified before a single-record `LATEST` pointer PUT. Run journals recover lost write responses; stale prepared runs cannot roll a newer baseline back. Retained snapshots link through `previousSnapshotKey`; an older link may point to a pruned record.

Local storage uses an exclusive file lock and atomic pointer rename. A crashed local process can leave `storage/erate-history/<monitor>/LOCK`; inspect its PID before manually clearing it. Do not delete history casually. History keeps the eight newest snapshots plus the current baseline and active recovery candidate: at most ten snapshots and ten associated journals, plus three metadata/pointer records. Each snapshot is capped at 10 MB, so retained snapshot payloads are bounded to 100 MB per monitor. Cleanup validates digests and the dedicated-store marker before deleting only recognized snapshot/journal records. Unrelated records, corruption or a 128-key scan limit fail closed. A denied cleanup after publication is reported as `retentionCleanupPending`; the next run retries cleanup before source work. Named storage continues to incur storage cost while idle. Recent same-run recovery is supported while its journal and default run storage remain available; default platform retention also applies. Older prepared runs fail with `run_journal_expired`; start a fresh run. No indefinite archive is promised.

Dataset/export writes happen from a prepared candidate before publication. Native export records and actual dataset rows are read back and checked against exact expected hashes before publication; delivery intent and verified acknowledgments are saved and read back in the default run store. An uncertain partial or invisible append is never repeated; recovery waits for the complete originally requested batch to become visible and stops baseline advancement until verification. An undated legacy prepared run without this delivery protocol requires review while its dataset is incomplete. An incomplete run may have prepared exports/rows, but its report explicitly says no new baseline was published. Use `OUTPUT.complete: true` and `baselinePublished: true` as the complete observation. If the final OUTPUT write fails after publication, ERROR explicitly says the baseline may already be committed; resurrect the same run to reconcile its persisted candidate and recover the report. Do not assume every FAILED run leaves the previous baseline unchanged. Snapshots contain all last-known-good evidence; dataset and change export contain baseline/new/changed rows only.

### Files and validation

`CONTRACTS.json` and spreadsheet-safe `CONTRACTS.csv` contain all retained contract evidence. `CHANGES.json`/`.csv` contain output changes/baseline rows. `UNRESOLVED.json` preserves missing identities. `OUTPUT` reports limits, completeness, source versions, observed/carried-forward counts, snapshot key, history store and pricing status. Nested exact changes and per-FRN provenance are best consumed as JSON.

Run `npm test` for offline acceptance tests. `npm run sample` executes the captured public fixtures and writes native exports in `artifacts/`; it makes no source requests or cloud runs. Fixture provenance is in `test/fixtures/PROVENANCE.json`. A live bounded private acceptance build/run is separate evidence; inspect the release report for its actual status and costs.

### Price and spending limits

**$0.01 per contract in a completed snapshot** ($10 per 1,000 contract snapshots), including unchanged contracts. For example, a complete seven-contract observation costs $0.07 even if it has no changes. The price counts every contract in `CONTRACTS.json`, including explicitly flagged last-known-good evidence retained from an earlier read. It does not count FRNs, annual funding years, changed fields or source requests separately. Empty complete snapshots count zero units. There are no actor-start or automatic dataset-item events, and no separate runtime-usage surcharge is requested.

Before delivery, the actor checks that your maximum run charge covers the entire selected/watched snapshot. If it does not, the observation stops without charging or publishing a new baseline; raise the cap or choose a narrower scope with a new monitor. `maxContracts` is a completeness guard: it does not silently truncate results. For a first trial, use one BEN and a maximum charge of at least $0.05 for the five-contract prefill. Invalid input, source errors, incomplete extraction and unverified delivery create no contract-snapshot charge.

A billable snapshot has verified native exports and dataset rows, a verified history pointer, and a verified complete OUTPUT report. Charging follows that completion. An interrupted run can therefore leave completed data awaiting billing. A late report/storage error can also make a run fail after a completed snapshot was charged; the completed data and `BILLING_STATE` receipt are retained. Check `OUTPUT.complete`, `baselinePublished`, `pricing.status`, `pricing.unitCount` and `BILLING_STATE` rather than assuming run status alone means charged or uncharged. An unchanged run has zero change rows in the dataset but its full contract export is still the billable result.

The actor writes and verifies a charge intent before its single full-snapshot event batch, and verifies a durable acknowledgment afterward. An acknowledged same-run recovery does not charge again while its journal and run storage are retained. A lost response may be retried with the same key only within a conservative two-minute window; the platform key expires after three minutes. Older ambiguous charges stop for review without another POST. Starting a fresh run is a fresh observation and can incur a fresh charge. Storage and charging have no shared transaction, and indefinite exactly-once recovery is not promised. Legacy unpriced prepared runs cannot be retroactively billed.

Apify storage retention and ordinary access to stored datasets/key-value records still apply; named monitor history remains stored between runs. Review your Apify usage and narrow or retire monitors you no longer need. Pricing does not establish buyer demand or willingness to pay.

No contacts, applicant directory redistribution, annual FRN cost sums, browser, proxy, OCR, LLM, enrichment or outreach are included. This actor preserves reported evidence; it does not predict procurement, confirm renewal deadlines or identify a current incumbent.

### Validation and recovery

Run `npm test` for source/semantic/storage acceptance, billing fault simulations, and real local process-interruption tests. Public fixture provenance is in `test/fixtures/PROVENANCE.json`. The release report records the exact private cloud build, scenarios, costs and remaining limitations; local simulations do not prove cloud billing permissions or a second customer's storage grants. `examples/output-priced-complete-illustrative.json` is an actual-runtime offline illustration with mocked charging. The older `output-complete-unchanged.json` and `output-incomplete-cap.json` retain genuine historical unpriced cloud evidence and do not claim paid acceptance.

For an incomplete read, inspect OUTPUT, narrow the scope or increase the relevant bounded cap, and start a fresh run. For a completed run with an unconfirmed recent charge or report failure, recover the same run while its retained state is available. An expired ambiguous charge requires review, not an automatic replay. Shared or foreign-account history stores are refused.

# Actor input Schema

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

One two-letter state. Defaults to NY only when BEN/SPIN filters are absent.

## `bens` (type: `array`):

At most 100 exact numeric BENs.

## `providerSpins` (type: `array`):

At most 100 nine-digit historical provider identifiers. Watch history is fetched without this discovery restriction.

## `fundingYears` (type: `array`):

One to ten years. Default current UTC year and previous two. Rolling years does not reset semantic comparisons.

## `reportedEndFrom` (type: `string`):

Optional YYYY-MM-DD filter on reported contract expiration, not an inferred rebid deadline.

## `reportedEndTo` (type: `string`):

Optional inclusive YYYY-MM-DD. Previously watched contracts remain watched outside the window.

## `monitorName` (type: `string`):

Reuse one name for one discovery scope; complete reads alone publish its baseline.

## `historyStoreId` (type: `string`):

Empty dedicated KVS owned by the current run account, selected with READ/WRITE grants. Cleanup deletes only validated history records; shared/foreign stores or unrelated keys are rejected. Omit to create a named run-account store.

## `maxContracts` (type: `integer`):

A reached limit produces incomplete output and preserves the last-known-good baseline.

## `maxRawRows` (type: `integer`):

A reached limit produces incomplete output and preserves the last-known-good baseline.

## `maxResponseBytes` (type: `integer`):

A reached limit produces incomplete output and preserves the last-known-good baseline.

## `pageSize` (type: `integer`):

A reached limit produces incomplete output and preserves the last-known-good baseline.

## `requestTimeoutSecs` (type: `integer`):

A reached limit produces incomplete output and preserves the last-known-good baseline.

## `maxRunSecs` (type: `integer`):

A reached limit produces incomplete output and preserves the last-known-good baseline.

## Actor input object example

```json
{
  "states": [],
  "bens": [
    "17027710"
  ],
  "providerSpins": [],
  "fundingYears": [
    2024,
    2025,
    2026
  ],
  "monitorName": "default",
  "maxContracts": 5,
  "maxRawRows": 1000,
  "maxResponseBytes": 10000000,
  "pageSize": 100,
  "requestTimeoutSecs": 60,
  "maxRunSecs": 120
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `report` (type: `string`):

No description

## `contractsCsv` (type: `string`):

No description

## `changesCsv` (type: `string`):

No description

## `contractsJson` (type: `string`):

No description

## `changesJson` (type: `string`):

No description

## `unresolvedJson` (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 = {
    "states": [],
    "bens": [
        "17027710"
    ],
    "fundingYears": [
        2024,
        2025,
        2026
    ],
    "maxContracts": 5,
    "maxRawRows": 1000,
    "pageSize": 100,
    "maxRunSecs": 120
};

// Run the Actor and wait for it to finish
const run = await client.actor("cauldo/erate-contract-evidence").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 = {
    "states": [],
    "bens": ["17027710"],
    "fundingYears": [
        2024,
        2025,
        2026,
    ],
    "maxContracts": 5,
    "maxRawRows": 1000,
    "pageSize": 100,
    "maxRunSecs": 120,
}

# Run the Actor and wait for it to finish
run = client.actor("cauldo/erate-contract-evidence").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 '{
  "states": [],
  "bens": [
    "17027710"
  ],
  "fundingYears": [
    2024,
    2025,
    2026
  ],
  "maxContracts": 5,
  "maxRawRows": 1000,
  "pageSize": 100,
  "maxRunSecs": 120
}' |
apify call cauldo/erate-contract-evidence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cauldo/erate-contract-evidence"
        }
    }
}
```

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/p5edqotaI9LekLuqM/builds/ow8SYwNqu9aYt39E1/openapi.json
