# UK Contract Expiry Radar - Recompete Leads (`datagrit/uk-contract-expiry-radar`) Actor

UK public contracts ending soon with incumbent supplier, buyer, value and contact - recompete leads from Contracts Finder award notices.

- **URL**: https://apify.com/datagrit/uk-contract-expiry-radar.md
- **Developed by:** [datagrit](https://apify.com/datagrit) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What does UK Contract Expiry Radar do?

UK Contract Expiry Radar turns Contracts Finder award notices (the UK government portal for contracts, without Find a Tender) into a list of public contracts that are about to end. For every contract it returns the incumbent supplier (with Companies House number), the buying organisation, the award value, the contract start and end dates, and how many days are left. Filter by expiry window, CPV code, keyword and value, then export the result as JSON, CSV or Excel, call it through the Apify API, or plug it into n8n, Make and AI agents through MCP.

### Who is it for?

- **Bid and sales teams** who want to know which public contracts come up for recompete in the next 6 to 18 months, before the tender is published.
- **Consultancies and agencies** that sell to a specific buyer type and want a shortlist of incumbents to approach.
- **Market analysts** who track contract length, extension options and supplier concentration by sector.

### How to use it

1. Set the **expiry window**: for example 0 to 12 months to list contracts that end within a year, or 6 to 18 months to skip the ones already too close to tender.
2. Optionally add **keywords** (title, description, buyer or supplier), **CPV prefixes** (for example `72` for IT services or `45` for construction) and a **minimum value**.
3. Choose how far back to read award notices, then run the Actor.
4. Download the dataset or fetch it from the API.

Contracts Finder publishes roughly one to two hundred award notices per working day (1 to 2 API requests) and very few at weekends (0 to 9 per day), so a longer lookback window reads more notices and takes longer. The default is 7 days with a 240-second time budget; raise both together for a longer scan.

### Example output

| title | buyerName | supplierName | awardValue | contractEnd | daysToExpiry |
|---|---|---|---|---|---|
| Managed IT support services | Example Borough Council | Example Systems Ltd | 480000 | 2027-03-31T00:00:00.000Z | 182 |

```json
{
  "id": "example-award-id",
  "title": "Managed IT support services",
  "buyerName": "Example Borough Council",
  "supplierName": "Example Systems Ltd",
  "supplierCompanyNumber": "01234567",
  "awardValue": 480000,
  "currency": "GBP",
  "contractStart": "2024-04-01T00:00:00.000Z",
  "contractEnd": "2027-03-31T00:00:00.000Z",
  "daysToExpiry": 182,
  "contractLengthMonths": 36,
  "cpvCode": "72000000",
  "frameworkCallOff": false,
  "callOffMentioned": false,
  "extensionMentioned": true,
  "recordDisputed": false,
  "disputedFields": null,
  "disputedOtherValues": null,
  "found": true
}
```

### What data do you get?

Each record includes the notice title and description, buyer name, locality and website, incumbent supplier with Companies House number and company size, all suppliers for multi-supplier awards, award value and currency, award and publication dates, contract start and end dates, days and months to expiry, contract length, main and additional CPV codes, procurement method, a framework call-off flag taken from the procurement procedure field, a separate flag for call-off wording in the text, an extension-clause flag, SME suitability, a link to the original notice and a dispute marker (`recordDisputed`) for the rare award that was published twice with conflicting values. The buyer contact email is off by default and can be switched on in the input.

### How much does it cost?

You pay per contract returned. Pricing depends on your Apify plan: a small fee when a run starts, then a price per result that is lower on paid plans. The Apify free plan includes monthly credit you can use to try it. You can set a maximum spend on the run: the Actor stops when the limit is reached. It reads a public government API over plain HTTP, so runs are fast and light on platform resources.

### Input

- **Keywords** – optional; a contract is kept when its title, description, buyer or supplier contains any of them (the award and tender descriptions are searched in full, although the `description` field in the result is shortened to 800 characters).
- **Expires after / before (months)** – the contract end-date window, counted from today.
- **Publication lookback (days)** – how many days of award notices to read (default 7).
- **Maximum run time (seconds)** – time budget for reading the source (default 240, 0 = no limit). When it is reached the Actor keeps everything found so far and reports how many lookback days it covered.
- **CPV prefixes** – optional; keep contracts whose main or additional CPV code starts with a prefix.
- **Minimum value** – skip awards below this amount. Awards published without a value (the source reports 0, shown as null) are skipped when a minimum above 0 is set.
- **Include framework call-offs** – turn off to drop notices whose procurement procedure is a call-off from a framework agreement or a dynamic purchasing system (in a 3-day sample of 417 awards, 178 and 68 of them, 59 percent). The check reads the procedure field only; `callOffMentioned` marks notices that merely mention call-off in the text and they are kept.
- **Include contact email** – adds the contact published on the notice.
- **Maximum results** – total limit for the run.

### Is it legal to use this data?

The Actor reads only information the UK government publishes openly under the Open Government Licence through the Contracts Finder API. It does not log in or bypass any access control. Contact details, if you enable them, are personal data in some cases: you are responsible for using them in line with applicable data protection law. This description is not legal advice.

### FAQ

**What if the run returns nothing?** The run status tells you why. "Published no award notices in the last N days" means the source had nothing in the window (it is nearly empty at weekends, so a 1-day lookback on a Saturday or Sunday is often empty): raise the lookback days. If the source answers with empty days on working days while it is clearly throttling (it does this when one IP sends many requests), the Actor waits up to 3.5 minutes per day (within "Maximum run time"), re-checking against a control request, and then either continues without that day (named in the run status) or, if nothing could be read at all, fails with a clear message instead of returning an empty result: run it again later or turn on a proxy. "No contract matched your filters" means notices were read but none passed your expiry window, keywords, CPV or value filters: widen those.

**Does it list contracts that are not yet awarded?** No. It covers awarded contracts and shows when each one ends. Use the expiry window to find recompete opportunities early.

**Why do some contracts have no end date?** Some notices are published without a contract period. Those are skipped, because the expiry window cannot be applied.

**How current is the data?** Every run reads the live Contracts Finder API for the lookback window you choose.

**How long does a run take?** Contracts Finder allows 12 requests per 120 seconds. A working day costs 1 or 2 requests (a day with more than 100 notices needs a second page), a weekend day 1. The default 7-day window comes to 11 to 12 requests (measured on the live feed: 11 or 12 requests and several hundred award records per week; the exact number changes from week to week), which fits inside the limit: reading the whole week takes about 15 seconds with no pause. The count is not fixed: a single day above 200 notices (the busiest measured day had 193) would add a request, and the 13th request waits about two minutes for the window to reopen - the run then takes around 2.5 minutes, still inside the default 240-second budget. Windows longer than a week need more than 12 requests, and then the Actor pauses about two minutes after every further 12 requests (roughly 20 seconds per extra working day). The run also stops at the run-time budget (240 seconds by default): the results found so far are kept and the run status says how many lookback days were read. For a longer lookback window raise both the lookback days and the maximum run time (0 removes the limit).

**Which sources does it cover?** Contracts Finder only. Contracts published only on Find a Tender (the above-threshold notices) are not included, and some other UK tender Actors on the Store read both portals. The nearest Actor on the same source, dataio/uk-public-contract-awards, also reads only Contracts Finder and filters by the days left to contract end, so the expiry view is not what sets this Actor apart. Choose this Actor when you care about how the award rows are built: a call-off flag taken from the procurement procedure field (`frameworkCallOff`) next to a separate flag for call-off wording in the text (`callOffMentioned`), an extension-clause flag (`extensionMentioned`), one row per award with corrected and republished notices merged field by field and conflicting values marked (`recordDisputed`), and an expiry window counted in months ahead.

**What if a notice was corrected and published twice?** You get one row per award, merged field by field. On the live feed the two copies of a duplicated award carry the same award date (12 of 12 pairs in a 7-day scan), so the award date cannot tell which copy is right. The rules are:

- A missing value never beats a real one. When one copy has no award value (the source publishes 0, shown as null), no end date or no supplier and the other has the data, the row carries the data (in the scan above one award was published once with a value of 0 and once with 150,263.40 GBP; the row shows 150,263.40). Days, months to expiry and contract length are recomputed from the merged dates.
- When both copies have a value and the values differ (for example an award value of 504,113 against 50,411,351, or an end date a year apart), the Actor cannot know which is right. It keeps the copy with the later award date, or the later one in the feed when the dates are equal, and marks the row: `recordDisputed` is true, `disputedFields` lists the fields and `disputedOtherValues` shows the values it did not use (for example `awardValue=504113`). Check those rows against the notice link before you rely on the value; in the 7-day scan 5 of 586 rows were disputed.
- `frameworkCallOff` is recomputed from the procedure of the kept copy; `callOffMentioned` and `extensionMentioned` are true when either copy says so. A newer publication day always wins over an older one.

The run status reports how many duplicates were merged, how many carried different data, how many rows had a missing value copied in from the other copy (not counted when the copy that was kept already had the value) and how many disputed contracts are in the result (disputed award records removed by your filters are counted separately and do not appear in the dataset).

**Can I schedule it?** Yes, use Apify schedules or call the Actor from your own workflow.

**Something looks wrong.** Open an issue with the input you used; layout or API changes at the source are fixed quickly.

### Related Actors

Other public-data Actors from the same publisher are listed on the Store profile.

# Changelog

This Actor's version history is a separate document: https://apify.com/datagrit/uk-contract-expiry-radar/changelog.md

# Actor input Schema

## `queries` (type: `array`):

Optional. A contract is returned when its title, award and tender descriptions, buyer or supplier name contains any of these keywords (case-insensitive; the description is searched in full even though the result shows it shortened to 800 characters). Leave empty to list every contract in the expiry window. Keywords narrow results but make the scan read more notices.

## `expiresAfterMonths` (type: `integer`):

Earliest contract end date, counted in months from today. 0 includes contracts that end from today on; use 6 to skip contracts ending in the next six months.

## `expiresBeforeMonths` (type: `integer`):

Latest contract end date, counted in months from today. 12 lists contracts that come up for recompete within a year.

## `publishedLookbackDays` (type: `integer`):

How far back to read Contracts Finder award notices, newest first; the scan stops as soon as Maximum results is reached. Roughly one to two hundred award notices are published per working day (1 to 2 API requests; very few at weekends) and the source allows 12 requests per 120 seconds (the Actor paces itself), so the default 7 days (11 to 12 requests, about 15 seconds) normally never waits, and each further working day of lookback adds roughly 20 seconds of pause. Long contracts (3-5 years) need a long window to appear as expiring - raise this together with Maximum run time.

## `maxRunSeconds` (type: `integer`):

Hard time budget for reading the source. When it is reached the Actor stops, keeps every contract found so far and says how many lookback days it covered in the run status. 0 removes the limit (needed for long lookback windows with strict filters).

## `cpvPrefixes` (type: `array`):

Optional. Keep only contracts whose CPV code (main or additional) starts with one of these prefixes, for example 72 for IT services or 45 for construction.

## `minValue` (type: `integer`):

Skip contracts with an awarded value below this amount. Awards published without a value (the source reports 0) are skipped as soon as a minimum above 0 is set. 0 keeps everything, including awards without a value.

## `includeFrameworkCallOffs` (type: `boolean`):

Call-offs from frameworks such as G-Cloud and from dynamic purchasing systems are usually short and re-bought through the same route. Turn off to drop every notice whose procurement procedure says call-off from a framework agreement or a dynamic purchasing system. Notices that only mention call-off in their text are kept.

## `includeContactEmail` (type: `boolean`):

Adds the contact email the buyer published on the notice. Off by default; when you turn it on, you are responsible for using it in line with data protection rules.

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

Stop after this many contracts.

## `proxyConfiguration` (type: `object`):

Optional proxy. Contracts Finder is a public government API and normally needs none; enable Apify Proxy only if the run fails with a message that the source is throttling your IP address.

## Actor input object example

```json
{
  "queries": [],
  "expiresAfterMonths": 0,
  "expiresBeforeMonths": 12,
  "publishedLookbackDays": 7,
  "maxRunSeconds": 240,
  "cpvPrefixes": [],
  "minValue": 0,
  "includeFrameworkCallOffs": true,
  "includeContactEmail": false,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All extracted records as a dataset.

# 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 = {
    "queries": [],
    "expiresAfterMonths": 0,
    "expiresBeforeMonths": 12,
    "publishedLookbackDays": 7,
    "maxRunSeconds": 240,
    "cpvPrefixes": [],
    "minValue": 0,
    "includeFrameworkCallOffs": true,
    "includeContactEmail": false,
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagrit/uk-contract-expiry-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 = {
    "queries": [],
    "expiresAfterMonths": 0,
    "expiresBeforeMonths": 12,
    "publishedLookbackDays": 7,
    "maxRunSeconds": 240,
    "cpvPrefixes": [],
    "minValue": 0,
    "includeFrameworkCallOffs": True,
    "includeContactEmail": False,
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("datagrit/uk-contract-expiry-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 '{
  "queries": [],
  "expiresAfterMonths": 0,
  "expiresBeforeMonths": 12,
  "publishedLookbackDays": 7,
  "maxRunSeconds": 240,
  "cpvPrefixes": [],
  "minValue": 0,
  "includeFrameworkCallOffs": true,
  "includeContactEmail": false,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call datagrit/uk-contract-expiry-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagrit/uk-contract-expiry-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/ijvYvbMkN7ZupN5Ig/builds/PIZXpjIS8wdDkHz1Y/openapi.json
