# Expiring Federal Contracts – Recompete Radar (`cce_partners/govcon-recompete-radar`) Actor

Federal contracts ending in 3-18 months (USAspending.gov) with incumbent and SAM.gov check. Soonest 25 per run, or only new/changed with a saved monitor name.

- **URL**: https://apify.com/cce_partners/govcon-recompete-radar.md
- **Developed by:** [Chris Grant](https://apify.com/cce_partners) (community)
- **Categories:** Lead generation, Automation
- **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

## Expiring Federal Contracts – Recompete Radar

**Federal recompete tracker** for federal contracts whose period of performance ends in **3–18 months**, from official **USAspending.gov** data. Give it a **monitor name** and each run returns only **new or changed** contracts; without one, a run returns the **25 soonest-ending** contracts. **Most rows are expiring contracts with no SAM.gov notice yet: this is a recompete pipeline, and only some rows are matched to an open SAM.gov notice.** Each contract is checked against open SAM.gov notices (sources sought, pre-solicitation, solicitation, J\&A/extension) with an honest **match confidence**. Each row is one pipeline opportunity: incumbent, UEI, value, end date, agency, NAICS/PSC, and "already on SAM.gov? Y/N + link". No API key needed. No personal contact data.

Part of the **GovCon suite** by CCE Partners:

- **Expiring Federal Contracts – Recompete Radar** (this Actor): what is about to be re-competed, and is it on SAM.gov yet?
- **[SAM.gov Scraper – New Contract Opportunity Alerts](https://apify.com/cce_partners/samgov-opportunity-radar)**: new and amended SAM.gov opportunities by NAICS, set-aside, agency and state.
- **[SEC Form D Scraper – New Startup Funding Raises](https://apify.com/cce_partners/sec-form-d-new-raises-feed)**: newly funded private companies (prospective teaming partners and commercial buyers).
- **[Federal Contract Awards – New Winners Feed](https://apify.com/cce_partners/federal-contract-award-winners-feed)**: who just won federal contracts in your NAICS (the start of the next contract clock), with the current parent company; only new awards with a monitor name.

### Why this

- **Recompetes before the RFP.** Contracts ending in 3–18 months are where capture starts. Set your own window.
- **Joined to SAM.gov.** See whether a sources sought, pre-sol, solicitation or bridge/extension J\&A is already posted, with the reason it matched.
- **Honest match confidence.** `high` / `medium` / `low` / `none`, explained below. Only `high` and `medium` count as `onSam`.
- **Only new or changed.** Each watch remembers what it returned. A contract comes back as `changed` when its award data changes (current end date, award amount or incumbent UEI; `changeReason: award`) or when a new high- or medium-confidence SAM.gov notice matches it (`changeReason: samNotice`, checked only on runs where the SAM.gov join worked). Other SAM.gov fields don't count, low-confidence matches never trigger it, and a SAM.gov outage never re-sends rows.
- **Competitor watch.** Give incumbent names or UEIs; get their expiring contracts.
- **No personal data.** SAM.gov contact names, emails and phones are never read into output. Recipients that look like individuals (sole proprietors) have name and UEI removed.
- **Official sources, polite.** USAspending API at ≤1 request/second (the published limit is ~1,000 per 300 s), sequential, with retries. SAM.gov public nightly extract: one download per run.

### Use cases

#### 1. Capture manager: what's expiring in my lane?

```json
{ "naicsCodes": ["541512"], "awardingAgencies": ["Department of Veterans Affairs"], "minAwardAmount": 1000000, "monitorName": "va-it" }
```

#### 2. BD lead: watch competitors' expiring contracts

```json
{ "incumbents": ["D84JK2RNL7V4", "Peraton"], "monthsFrom": 3, "monthsTo": 24, "monitorName": "competitors" }
```

#### 3. Small business: facilities work in my states

```json
{ "naicsCodes": ["561210", "561720"], "placeOfPerformanceStates": ["VA", "MD"], "minAwardAmount": 250000 }
```

Schedule daily or weekly; connect to Sheets, Slack, Zapier or Make.

### What you get

| Field | Example |
|---|---|
| `piid` / `parentIdvPiid` | 36C10B23N10010013 / VA11816D1003 |
| `incumbent` / `incumbentUei` | GOVCIO, LLC / D84JK2RNL7V4 |
| `currentParentName` / `currentParentBasis` | Lockheed Martin Corporation / alias-map (for Sikorsky Aircraft; see Parent companies) |
| `usaspendingParentName` / `usaspendingParentUei` | RTX CORP / PPLZG8J3N9D4 (USAspending's parent, unchanged) |
| `currentParentUei` / `currentParentSource` | UEI when the parent comes from USAspending / alias id + cited source URL |
| `awardingAgency` / `awardingSubAgency` | Department of Veterans Affairs |
| `naicsCode` / `pscCode` | 541512 / DA01 |
| `awardAmount` | 8657621.89 |
| `startDate` / `endDate` / `monthsToEnd` | 2026-02-16 / 2027-01-15 / 3 |
| `potentialEndDate` | 2027-01-15 (end date if all options are exercised; null if not looked up) |
| `onSam` / `samMatchConfidence` / `samMatchReason` | true / high / award PIID referenced in notice |
| `samNoticeTitle` / `samNoticeType` / `samUrl` | DA10--Cloud Operations ... Extension / Justification / link |
| `usaspendingUrl` | link to the award on USAspending.gov |
| `changeType` | new / changed (runs with a monitor name; empty otherwise) |
| `changeReason` | award (end date, value or incumbent changed) / samNotice (a new high- or medium-confidence SAM.gov notice for an existing contract) |

Full field list: `.actor/dataset_schema.json`. Request counts and join stats for each run are saved to the run's key-value store as `RUN_STATS`.

### How the SAM.gov match works

Open notices only: active, posted in the last 12 months, response deadline not passed, and not Award Notices.
| Confidence | Rule |
|---|---|
| **high** | The contract's PIID appears in the notice's solicitation number, title or description, **or** a same-agency/NAICS notice names the incumbent |
| **medium** | The parent IDV/vehicle PIID appears in the notice, **or** same awarding sub-agency + NAICS + PSC + place-of-performance state |
| **low** | Same awarding sub-agency + NAICS only. Same buyer and type of work, often a different requirement. Not counted as `onSam` |
| **none** | No open notice found |

Most recompetes are not on SAM.gov yet 3–18 months out, so expect many `none`/`low` rows. A `high` row usually means a follow-on, recompete or bridge extension is already in motion. With a SAM.gov API key the join is real-time but cannot search descriptions (so fewer `high` matches).

### Expiring government contracts by NAICS

Give one or more NAICS codes (optionally PSC codes, awarding agencies, place-of-performance states and a minimum award amount) and a window such as 3–18 months. You get every federal contract in that lane whose current period of performance ends in the window, with incumbent, UEI, value, end date and whether a related notice is already open on SAM.gov.

### Current vs potential end date

- `endDate` is the **current** period-of-performance end date: the period exercised so far. This is what USAspending search returns, and the window is always applied to it.
- `potentialEndDate` is the end date if **all options** are exercised. USAspending only has it on each award's detail page, so the Actor looks it up one contract at a time (1 request per second, soonest-ending first, up to `maxPotentialEndLookups`, default 20, which keeps a default run to about 2 minutes; raise it for more rows, about 1 second each). Rows past the cap have `potentialEndDate: null`.
- When `potentialEndDate` is later than `endDate`, the agency can extend the contract with options instead of recompeting it yet.
- Set `endDateBasis` to `potential` to drop contracts whose options can run past your window. Contracts whose current end is before the window but whose options reach into it are not found.

### Parent companies (current, not stale)

USAspending's parent for a contractor is often out of date. For example, Sikorsky is still listed under RTX (Lockheed Martin bought it in 2015), and Northrop Grumman Systems was listed under "Titan II Inc." (old Northrop Grumman Corporation's name after the 2011 reorganization). Each row has:

- `usaspendingParentName` / `usaspendingParentUei`: the parent exactly as USAspending's recipient profile gives it (unchanged).
- `currentParentName`: the current parent. It comes from a small alias map checked into the Actor when the USAspending parent is known to be stale (`currentParentBasis: "alias-map"`, with the cited SEC filing, government report or press release in `currentParentSource`). Otherwise it is the USAspending parent (`"usaspending-parent"`, with `currentParentUei`).
- Alias map (Oct 2026): Northrop Grumman (Titan II), Sikorsky → Lockheed Martin, Huntington Ingalls, General Dynamics units (Bath Iron Works, Electric Boat, NASSCO, GDIT), L-3 / L3 Technologies → L3Harris, DynCorp / PAE → Amentum, Vectrus → V2X, Cognosante → Accenture, Salient CRGT → GovCIO, Oracle Health Government Services → Oracle.
- Recipient profiles are looked up once per unique incumbent (up to `maxParentLookups`, default 50, 1 request per second), in parallel with the other steps, and cached for 30 days (7 days when USAspending has no parent), so they add about 15 s on the first run and almost nothing after that. If the recipient endpoint fails 3 times in a row or lookups take more than 60 s, the run stops looking up and uses the alias map only. `maxParentLookups: 0` turns lookups off; the alias map still applies by incumbent name. Individuals (redacted) get no parent.

### Recompete tracker in Google Sheets or n8n

Give each watch a **monitor name** (`monitorName`; the older `stateKey` field still works and keeps its saved state) and schedule it weekly or daily. Each run returns only new or changed contracts, so you can append them to Google Sheets or post them to Slack with Apify's integrations, or pull them into n8n with the Apify node. A contract also comes back as changed (`changeReason: samNotice`) when SAM.gov gets a new high- or medium-confidence notice for it. Low matches never trigger this, and it is only checked on runs where the SAM.gov join worked.

Without a monitor name nothing is remembered: every run returns the 25 soonest-ending contracts (`maxItems` changes this) and the status message reminds you to set a monitor name.

**Already ran it without a monitor name before (up to 0.2.2)?** Then your existing saved state is kept. Those versions remembered results per set of filters, and a run with the same filters and no monitor name still finds that state and returns only new or changed contracts, charging only those. The log and status message say so and give the monitor name that keeps this explicitly (a 12-character code; set it as `monitorName` or `stateKey` and the same saved state is used). Changing any filter means a new search with no saved state, so it runs stateless. One exception: a run with the Store's default filters (the prefilled NAICS 541512, Department of Veterans Affairs, $1,000,000+ and the default 3–18 month window, which is also what an empty input `{}` uses) is always stateless and returns the soonest-ending contracts, even if saved state exists for it. Such a run doesn't open, read or write the Actor's saved state at all (since 0.2.10), so that saved state is left untouched and no notice about it is shown. If you ran this Actor up to 0.2.2 with the default filters and no monitor name, set monitorName to 813100bcbbf9 to continue from that saved state. **This means a run with the Store's default filters and no monitor name is now charged for up to 25 rows every run, even if nothing changed.** To keep paying only for new or changed contracts, set a monitor name (`monitorName`).

**Overlapping runs.** Don't start two runs of the same monitor at once. The second one sees the first one's lock, stops without returning or charging rows, and says so in its status message (the platform still charges its run start). The lock is released when a run ends or fails, on a graceful abort (the default Abort in Console), and shortly before the run timeout (a monitor run stops itself about 15 s early, keeps what it already returned and says so). A forced abort or an out-of-memory crash kills the run at once, so it can't release the lock: the monitor is then blocked for up to 10 minutes, and runs started in that window stop without returning or charging rows (the platform still charges their start). If a run dies without releasing it, the next run checks that run's status and takes over at once if it is no longer running. If the status can't be read, the lock is taken over after 10 minutes without a heartbeat (a running Actor refreshes it every minute).

**Slow sources.** A run doesn't hang on a slow source. SAM.gov's nightly extract is dropped after 60 s with no data or 6 min in total; contract-detail and parent-company lookups stop after 3 failures in a row or 60–90 s. Rows are still returned, just without that enrichment, and the status message says so. A rejected SAM.gov API key is reported plainly and the join is skipped (no rows are re-sent or charged because of it).

### Pricing (pay per event)

- $0.01 per run start (per GB of memory, min 1)
- $0.01 per pipeline row returned (every returned row is charged; each row is an expiring contract, and only some are matched to an open SAM.gov notice)

With a monitor name, unchanged rows are not returned (and not charged) unless you turn on `emitUnchanged`. Without one (including runs with the Store's default filters), every run returns and charges up to 25 rows. Use `maxItems` to cap spend.

### FAQ

**How fresh is it?** USAspending contract data typically lags FPDS by days to weeks. The keyless SAM.gov extract is refreshed nightly (up to ~24 h old); add your own SAM.gov API key for real-time notices.
**Do I get duplicates?** Not with a monitor name: runs with the same monitor name return only new or changed contracts, and a run that is interrupted mid-way never charges a row twice (rows of an interrupted batch are re-sent free). Without a monitor name every run returns the soonest-ending contracts again.
**Does it include personal data?** No, by design.
**Is it affiliated with USAspending.gov, GSA or SAM.gov?** No. It uses their public data.

### Limits

- With no filter at all (e.g. API input `{}`), the run uses the Store defaults (NAICS 541512, Department of Veterans Affairs, $1M+ awards) and says so in the status message.
- Contracts only (award types A–D: BPA calls, purchase orders, delivery orders, definitive contracts). IDVs themselves are not listed; orders show their `parentIdvPiid`.
- `endDate` is the **current** period of performance end (USAspending search). `potentialEndDate` (all options exercised) comes from a separate USAspending award lookup, one request per contract (1 per second, up to `maxPotentialEndLookups`, default 20, soonest-ending first); rows beyond the cap have `potentialEndDate: null`. The window always uses the current end date. Set `endDateBasis: "potential"` to also drop contracts whose options can run past the window end. Contracts whose current end is before the window but whose options reach into it are not found.
- USAspending has no end-date filter: the Actor pages through contracts sorted by end date, so broad searches can hit `maxPagesPerQuery`. Narrow by NAICS, PSC, agency or amount.
- Sub-agency matching uses FPDS sub-tier codes. For agencies without sub-tiers (e.g. VA), `low` matches are broad.

# Changelog

This Actor's version history is a separate document: https://apify.com/cce_partners/govcon-recompete-radar/changelog.md

# Actor input Schema

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

6-digit (or 2-4 digit prefix) NAICS codes, e.g. 541512. At least one of NAICS, PSC, agency or incumbents is required.

## `pscCodes` (type: `array`):

Product/Service codes, e.g. DA01, R499.

## `awardingAgencies` (type: `array`):

Top-tier agency names exactly as on USAspending.gov, e.g. 'Department of Veterans Affairs'.

## `incumbents` (type: `array`):

Watch mode. Returns expiring contracts held by these companies. A 12-character UEI matches exactly; a name matches within the recipient name. Max 25.

## `minAwardAmount` (type: `integer`):

Skip small contracts.

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

2-letter US state codes, e.g. VA, MD.

## `monthsFrom` (type: `integer`):

Start of the end-date window.

## `monthsTo` (type: `integer`):

End of the end-date window.

## `endDateBasis` (type: `string`):

The window always uses the CURRENT period-of-performance end (what USAspending search returns). 'potential' also drops contracts whose options can run past the window end (needs potential-end lookups, capped below).

## `maxPotentialEndLookups` (type: `integer`):

potentialEndDate needs one USAspending request per contract (1 per second, soonest-ending first, within maxRequests). Default 20 keeps runs to about 2 minutes; raise it (up to 500) to fill potentialEndDate for more rows. 0 turns lookups off.

## `maxParentLookups` (type: `integer`):

currentParentName needs one USAspending recipient-profile request per unique incumbent (1 per second, run in parallel with the other steps, cached for 30 days). 0 = no lookups (the checked-in alias map still applies by incumbent name).

## `activityYears` (type: `integer`):

Only contracts with an action (award or modification) in the last N years.

## `joinSam` (type: `boolean`):

Match each contract to open SAM.gov notices (pre-solicitation, sources sought, solicitation, J\&A).

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

Optional. Leave empty to use the official SAM.gov public nightly extract (keyless, up to ~24h old). With a key, the join uses the real-time API but cannot search notice descriptions.

## `maxItems` (type: `integer`):

Max rows to return per run (you pay per row). Leave empty for the default: 25 without a monitor name, 100 with one.

## `maxPagesPerQuery` (type: `integer`):

100 contracts per page. Raise only for broad searches.

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

Hard budget. Requests are throttled to 1 per second.

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

Set a name (e.g. 'va-it') to get only NEW or CHANGED contracts: the Actor saves what this monitor already returned, and later runs with the same name return only contracts it hasn't returned yet, plus contracts whose end date, amount or incumbent changed, or that got a new high/medium SAM.gov notice. Use one name per filter set. Leave empty to get the soonest-ending contracts each run (nothing saved, up to 25 by default).

## `stateKey` (type: `string`):

Same as Monitor name (kept for existing integrations). If both are set, Monitor name wins.

## `emitUnchanged` (type: `boolean`):

With a monitor name: also return unchanged rows (each row is billed). Without a monitor name every run returns the soonest-ending contracts anyway.

## `resetState` (type: `boolean`):

Forget what this monitor has already returned (needs a monitor name).

## Actor input object example

```json
{
  "naicsCodes": [
    "541512"
  ],
  "awardingAgencies": [
    "Department of Veterans Affairs"
  ],
  "minAwardAmount": 1000000,
  "monthsFrom": 3,
  "monthsTo": 18,
  "endDateBasis": "current",
  "maxPotentialEndLookups": 20,
  "maxParentLookups": 50,
  "activityYears": 3,
  "joinSam": true,
  "maxPagesPerQuery": 30,
  "maxRequests": 200,
  "emitUnchanged": false,
  "resetState": false
}
```

# Actor output Schema

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

No description

## `runStats` (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 = {
    "naicsCodes": [
        "541512"
    ],
    "awardingAgencies": [
        "Department of Veterans Affairs"
    ],
    "minAwardAmount": 1000000
};

// Run the Actor and wait for it to finish
const run = await client.actor("cce_partners/govcon-recompete-radar").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 = {
    "naicsCodes": ["541512"],
    "awardingAgencies": ["Department of Veterans Affairs"],
    "minAwardAmount": 1000000,
}

# Run the Actor and wait for it to finish
run = client.actor("cce_partners/govcon-recompete-radar").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 '{
  "naicsCodes": [
    "541512"
  ],
  "awardingAgencies": [
    "Department of Veterans Affairs"
  ],
  "minAwardAmount": 1000000
}' |
apify call cce_partners/govcon-recompete-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cce_partners/govcon-recompete-radar"
        }
    }
}
```

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/wAzHi4V19oFQrEhUp/builds/guelmyf2DLmcHd2Ye/openapi.json
