# UK Public Contract Awards: Winners & Values (`mastazee_data_lab/uk-public-contract-awards-monitor`) Actor

Recently awarded UK public sector contracts from official Contracts Finder data: winning supplier, buyer, value and dates. Daily monitoring.

- **URL**: https://apify.com/mastazee_data_lab/uk-public-contract-awards-monitor.md
- **Developed by:** [Mastazee Data Lab](https://apify.com/mastazee_data_lab) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

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

## UK Public Contract Awards: Winners & Values

**See which suppliers have just won UK public sector contracts, who awarded them, for how much and for how long.** Every row is one award to one supplier, taken from the government's official Contracts Finder open data, with a link to the official notice. Run it once for a report, or schedule it daily and receive only newly published or corrected awards.

**What you get for each award:**

- the buyer
- the winning supplier, with its Companies House number when the notice gives one
- the award value (left blank when the buyer didn't publish one, never shown as £0)
- the award date and the contract start and end dates
- the procedure, CPV category and delivery region
- whether the supplier is an SME or VCSE
- the official notice link

### Three ways buyers use it

1. **Watch competitors and incumbents.** Filter by supplier name or by your sector's CPV codes, schedule it daily, and see every new win as it's published.
2. **Plan for re-tenders.** Contract end dates, incumbents and values show who holds which contract and when it is due to be re-procured.
3. **Size a public sector market.** Export a period's awards by CPV category, region or buyer into a spreadsheet or BI tool, and count awards and published values.

> **Tested on 25–26 October 2026,** against the live Contracts Finder API and in six runs on the Apify platform: exports, repeat runs and filtered monitors.
>
> - **Repeat runs:** a monitor run straight after a delivery returned 0 awards, repeating nothing.
> - **Separate filters:** monitors with different filters kept separate memories.
> - **Official records:** sampled award rows matched their official notices.
>
> This is a data tool, not a lead guarantee. Coverage is what public bodies publish on Contracts Finder (see *Data limitations*).

### Example input

```json
{
  "publishedFrom": "2026-10-19",
  "publishedTo": "2026-10-23",
  "cpvPrefixes": ["72"],
  "minValue": 100000
}
```

Weekly monitor of NHS and council construction wins in the North West and Yorkshire:

```json
{
  "lookbackDays": 30,
  "cpvPrefixes": ["45"],
  "regions": ["North West", "Yorkshire and the Humber"],
  "minValue": 250000,
  "onlyNewAwards": true,
  "stateKey": "northern-construction"
}
```

Filters can be combined. Text filters (`buyerContains`, `supplierContains`) are not case-sensitive and match if any listed text appears. `cpvPrefixes` takes the start of CPV codes (`72` = IT services, `45` = construction, `33` = medical equipment) and matches the main and additional codes.

### Example output (one row, trimmed)

```json
{
  "notice_id": "8d30e6ec-f30f-46a7-bcec-961a419a572f",
  "ocid": "ocds-b5fd17-bb439732-f359-40e7-9925-11073c6e5adf",
  "published_date": "2026-10-23",
  "buyer_name": "Ministry of Justice",
  "supplier_name": "Lumen Technologies UK Limited",
  "companies_house_number": "02495998",
  "award_value": 4920000.0,
  "currency": "GBP",
  "award_date": "2026-09-29",
  "contract_start_date": "2026-10-01",
  "contract_end_date": "2029-09-30",
  "procurement_method_details": "Call-off from a framework agreement",
  "cpv_main_code": "72261000",
  "cpv_main_description": "Software support services",
  "regions": [],
  "is_amended": false,
  "source_url": "https://www.contractsfinder.service.gov.uk/Notice/8d30e6ec-f30f-46a7-bcec-961a419a572f"
}
```

`OUTPUT_EXAMPLE.json` has three complete rows from the live test.

### Fields

| Field | Meaning |
|---|---|
| `award_key` | Stable key: `ocid`, award id and supplier. Use it to join runs |
| `notice_id`, `ocid`, `release_id`, `award_id` | Contracts Finder notice id (uuid), OCDS process id, id of the latest edit and award id |
| `notice_type`, `is_amended`, `release_count` | `award` or `awardUpdate`. `is_amended` is true when the notice was corrected. `release_count` is how many versions were seen in this run's window; only the latest is output |
| `delivery` | Incremental runs only: `new`, or `changed` for an award delivered before whose notice has since changed |
| `published_at`, `published_date` | When this version was published on Contracts Finder (UK time) |
| `award_date`, `award_status` | Award date entered by the buyer (can be months before publication) and status |
| `title`, `description` | Notice title and text, with emails and phone numbers removed |
| `buyer_id`, `buyer_name` | The contracting authority |
| `supplier_name`, `supplier_id` | Winner as published (`null` name when the buyer gave only a number) and Contracts Finder's id for it |
| `supplier_identifier_scheme`, `supplier_identifier`, `companies_house_number` | Registry id when given (30% of rows). Companies House numbers are padded to 8 characters |
| `supplier_is_sme`, `supplier_is_vcse` | The supplier's own flags when the buyer recorded them, else `null` |
| `awarded_to_n_suppliers` | Suppliers named on the award. Above 1 (frameworks), each supplier row repeats the **whole** award value. Don't add those rows up |
| `award_value`, `currency` | `null` when the buyer gave none (about 1 in 12 awards publish a value of 0, treated as unknown) |
| `tender_estimated_value` | The buyer's earlier estimate, when published |
| `contract_start_date`, `contract_end_date` | Contract period. Placeholder or impossible dates become `null` |
| `main_procurement_category`, `procurement_method`, `procurement_method_details` | goods / services / works, and the procedure as published |
| `cpv_main_code`, `cpv_main_description`, `cpv_codes`, `cpv_descriptions` | Main and additional CPV codes |
| `regions` | UK delivery regions named on the notice (can be empty or just `England`) |
| `tender_suitable_for_sme`, `tender_suitable_for_vcse` | Whether the buyer marked the tender as suitable (not the winner's status) |
| `source_url` | Official public notice page on Contracts Finder |

### Amended notices and duplicates

A corrected notice is published again with the same award id. Within one run only the latest version of each award and supplier is output (`is_amended: true`), and a notice repeated across pages is counted once. With `onlyNewAwards`, an award you already received comes back once more, flagged `delivery: "changed"`, only if something material changed (supplier, value, dates, buyer, title, procedure, CPV codes, regions). Re-saves with no change are not repeated.

### Data source and attribution

[Contracts Finder](https://www.contractsfinder.service.gov.uk/) OCDS API, run by the Cabinet Office. Published under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) (see the [site terms](https://www.contractsfinder.service.gov.uk/Home/TermsAndConditions)). No API key is needed.

**If you republish this data, include:** *Contains public sector information licensed under the Open Government Licence v3.0.* Every row links to its official notice. Note that Contracts Finder carries the content of notices as published by thousands of buyers; its operators give no guarantee of accuracy or completeness.

### Data limitations (please read)

- **Coverage is whatever buyers publish on Contracts Finder.** It is not every UK public contract: large above-threshold contracts are also published on Find a Tender, which this Actor does not read yet, and the devolved nations have their own portals.
- **Award values are the buyer's entry.** About 1 in 12 are blank, and framework "values" are often ceilings, not spend.
- **Publication date is not award date.** The date window filters on when a notice was last published or edited. Late and corrected notices carry old award dates.
- **Regions are often missing** (58% of rows in the live test) and are the delivery region, not the supplier's location.
- **Supplier identity is only as good as the notice.** Matching by name across buyers will have gaps; use `companies_house_number` when present.
- **Personal data.** Buyer contact names, emails and phones and supplier street addresses are never output, and emails and phone numbers found in the title or description text are replaced with `[email removed]` / `[phone removed]`. Supplier names are published as given, which for sole traders can be a person's name or a trading name containing one. Names of individuals typed into free-text descriptions cannot be reliably removed. If you process this data you remain the controller for your own use under UK GDPR.
- Corrected notices published after your window ended appear in your next run.

### Running time and cost

The Actor reads one UK day at a time, at about 1 request per second, because the API blocks heavy callers for 5 minutes. Measured on 25 October 2026:

| Run | Time | Rows |
|---|---|---|
| 7-day export, no filter (default) | 12 s, ~90 MB memory (132–247 s seen when the source is slow) | ~760 |
| Daily monitor, first run (7-day backfill) | 11 s | ~760 |
| Daily monitor, each later run | 4 s | new or corrected awards only: about 150–220 on a weekday, ~0 at weekends |

Platform usage is a fraction of a cent per run.

### Pricing

**$3.00 per 1,000 award rows** ($0.003 per row). There's no subscription and no extra charge per run beyond Apify's standard tiny start fee. Each row is one award to one supplier, and you are charged only for rows written to your dataset.

| How you use it | Rows | You pay (approx.) |
|---|---|---|
| One-off 7-day export, all awards | ~760 | ~$2.30 |
| Daily monitor, all awards, for a month | ~3,500 | ~$10–11 a month |
| Daily monitor filtered to one sector (e.g. CPV 72, IT services) | a few dozen to a few hundred a month | well under $1–2 a month |

- **No double billing:** in monitor mode an award is charged once, and again only if the buyer publishes a correction (flagged `delivery: "changed"`).
- **Failed runs:** a run that fails before reading any data outputs nothing and costs only the start fee.
- **Spending limits:** if you set a maximum cost per run, the Actor stops cleanly when it's reached. Awards it didn't deliver arrive on the next run.

### Troubleshooting

- **No rows?** The window may hold nothing that matches all filters. Widen `lookbackDays` or relax `minValue` (awards with no value are dropped when a minimum is set, unless `includeUnknownValue` is on) or `regions` (notices without a region are dropped).
- **"Contracts Finder refused the request (HTTP 403)":** the API rate-limits heavy callers for 5 minutes. The Actor already waits and retries once. Wait 5 minutes, then run again. Days already read in an incremental run are saved.
- **"could not be reached":** Contracts Finder is down or slow. Try again later.
- **Run says `partial`:** it stopped at `maxResults`, `maxRuntimeSeconds` or a source error. In incremental mode the next run continues where it stopped and repeats nothing.
- **"Only new awards" returned nothing:** everything in the window was already delivered to this `stateKey`. Use a new key to start over.
- **Same award twice in one dataset with different suppliers:** that is correct. One row per supplier per award.

### Known restrictions

- **Date limits:**
  - `lookbackDays` accepts 1 to 366 days.
  - For older history, set `publishedFrom` and `publishedTo` (up to 3,650 days per run). Use `maxResults` and Apify's per-run spending limit to keep large exports in budget.
  - A single day is limited to 40,000 notices, about 100 times a normal day. Past that, the run stops with an error rather than returning partial data.
- **Separate monitors:** each combination of `stateKey` and filters is its own monitor with its own memory. If a run starts while another run of the same monitor is still going, it stops without output, so nothing is charged twice.
- `onlyNewAwards` remembers up to 100,000 awards per state key. In that mode `publishedFrom` is only used on the first run; later runs continue from the saved position minus `overlapDays`.
- With `maxResults`, the newest awards are output first. In incremental mode the rest are delivered on the next run.
- A run that hits `maxRuntimeSeconds` outputs what it has read from completed days. Its summary says `status: partial`.
- Only award stage notices are read (tenders and planning notices are not).

# Actor input Schema

## `lookbackDays` (type: `integer`):

Awards published in the last this many days, including today (1 to 366). Ignored when 'Published from' is set, and after the first run when 'Only new awards' is on. About 100–170 award notices are published per working day. For older history, set 'Published from' and 'Published to'.

## `publishedFrom` (type: `string`):

First publication date to include, as YYYY-MM-DD (UK date). Amended notices count on the day they were last edited.

## `publishedTo` (type: `string`):

Last publication date to include, as YYYY-MM-DD. Leave empty for today.

## `buyerContains` (type: `array`):

Keep awards whose buyer name contains any of these texts (not case-sensitive), e.g. "NHS" or "Transport for London".

## `supplierContains` (type: `array`):

Keep awards won by suppliers whose name contains any of these texts (not case-sensitive), e.g. "Capita".

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

Keep awards with any CPV code starting with one of these digits, e.g. 45 (construction), 72 (IT services), 3314 (medical equipment). Matches the main code and the additional codes.

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

Keep awards delivered in these regions. Many notices name no region, or only 'England'; those are dropped when this is set.

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

Keep awards worth at least this much. Awards with no published value (about 1 in 12) are dropped unless 'Keep awards with unknown value' is on.

## `includeUnknownValue` (type: `boolean`):

Only matters when a minimum value is set.

## `onlyNewAwards` (type: `boolean`):

For scheduled monitoring: leave out awards already delivered by a previous run with the same state key. Awards whose notice was amended since come back flagged delivery = "changed".

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

Name for this monitor's memory when 'Only awards not seen in earlier runs' is on. Each combination of state key and filters is its own monitor, so changing the filters starts a fresh one automatically. If a run starts while another run of the same monitor is still going, it stops without output so nothing is charged twice.

## `overlapDays` (type: `integer`):

Incremental runs re-read this many days before the saved position, to catch notices published late. Already delivered awards are not repeated.

## `maxResults` (type: `integer`):

Stop after this many rows. Leave empty for no limit. In incremental mode the rest come on the next run.

## `maxRuntimeSeconds` (type: `integer`):

Stop reading after this long and output what was found. The summary says how far the scan got.

## Actor input object example

```json
{
  "lookbackDays": 7,
  "includeUnknownValue": false,
  "onlyNewAwards": false,
  "stateKey": "default",
  "overlapDays": 2,
  "maxRuntimeSeconds": 3300
}
```

# Actor output Schema

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

UK public contract award rows from Contracts Finder: buyer, winning supplier, value, dates, CPV codes, region and the official notice link.

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

Window scanned, rows output, awards already delivered in monitor mode, data-quality counts, stop reason and licence statement.

# 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 = {
    "lookbackDays": 7
};

// Run the Actor and wait for it to finish
const run = await client.actor("mastazee_data_lab/uk-public-contract-awards-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 = { "lookbackDays": 7 }

# Run the Actor and wait for it to finish
run = client.actor("mastazee_data_lab/uk-public-contract-awards-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 '{
  "lookbackDays": 7
}' |
apify call mastazee_data_lab/uk-public-contract-awards-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mastazee_data_lab/uk-public-contract-awards-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/8jGZJwtG2vobw0I4z/builds/anWuhdEruxTT2R90T/openapi.json
