# UK Public Contract Award Leads - Contracts Finder (`scrapersdelight/contracts-finder-awards-scraper`) Actor

From $0.002 per award notice, no start fee. Every UK public contract AWARDED on Contracts Finder: winning supplier, value, CPV code, contract start and end date - plus the buying authority's procurement contact, email and phone. Filter by CPV, keyword, region, buyer or value. No API key.

- **URL**: https://apify.com/scrapersdelight/contracts-finder-awards-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 per award notice returneds

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 Award Leads — Contracts Finder

Every contract the UK public sector **actually awarded**: what it was for, what it is worth, when it
ends, who won it — and the **named procurement officer at the buying authority, with their email and
phone number**.

Official [Contracts Finder](https://www.contractsfinder.service.gov.uk/) OCDS data, published by the
Cabinet Office under the **Open Government Licence v3**. **No API key. No account. No login.**

***

### The measured numbers

Everything below was counted on this machine on **2026-09-16**, by walking the feed to exhaustion —
not estimated, and not taken from the source's documentation.

| | |
|---|---|
| **August 2026, walked to exhaustion** | 28 pages, **2,711 award notices**, 16.2 MB, cursor terminated cleanly |
| **July 2026, walked to exhaustion** | 34 pages, **3,356 award notices** |
| Of the August month | 2,521 original awards + **190 amendments** (`awardUpdate`, 7.0%) |
| Unique buying authorities | **509** |
| Unique buyer email addresses | **860** |
| Unique winning suppliers | **1,916** (from 2,971 supplier records) |
| Total value awarded in the month | **£5,354,598,580** |
| Median award | **£100,608** |
| Contracts whose end date is still in the future | **2,453 of 2,711 (90.5%)** |
| Run rate | **2,700–3,400 notices/month**, ≈**35,000/year** |
| History | addressable back to **2015** (see honest limits) |

### Field fill — counted, not claimed

Three independent real samples. The headline column is a **full calendar month walked to exhaustion**
(n=2,711). The second is the 200 notices shipped as this Actor's test fixtures (published
2026-09-15). The third is a **real run of this Actor** over the 7 days to 2026-09-16 (n=741).

| field | Aug 2026 (n=2,711) | 2026-09-15 (n=200) | live run, 7 days (n=741) |
|---|---|---|---|
| `buyerEmail` | **100.0%** | **100.0%** | **100.0%** |
| `buyerPostcode` · `buyerStreet` · `buyerName` | 100.0% | 100.0% | 100.0% |
| `buyerContactName` (a named person) | **69.8%** | 54.0% | **52.8%** |
| `buyerPhone` | **51.6%** | 35.0% | **37.8%** |
| `buyerLocality` | 100.0% | 73.5% | — |
| `awardValueGbp` · `awardDate` · `contractStartDate` · `contractEndDate` | 100.0% | 100.0% | 100.0% |
| `bidDeadline` · `cpvCode` · `cpvDescription` · `title` | 100.0% | 100.0% | 100.0% |
| `supplierName` | 100.0% | 100.0% | 100.0% |
| `supplierAddress` | 99.2% | 100.0% | — |
| `supplierPostcode` (regex-extracted from the address blob) | 91.1% | 89.0% | **90.8%** |
| `supplierIsSme` | 78.9% | 83.5% | — |
| `supplierCompanyNumber` (Companies House) | **33.7%** | 40.5% | **41.7%** |
| `procurementCategory` | 98.9% | 99.5% | — |
| `procurementMethodDetails` | 87.3% | 95.0% | — |
| `tenderValueGbp` | 80.4% | 91.0% | — |
| `procurementMethod` | 76.4% | 90.0% | — |
| `deliveryRegions` | 54.5% | 40.0% | — |
| `deliveryPostcodes` | 43.4% | 39.5% | — |
| `additionalCpvCodes` | 13.3% | 12.5% | — |

`buyerContactName` and `buyerPhone` swing by ~15 points between August and September — September is
consistently the weaker month in both independent September samples. Every figure is printed here
rather than only the flattering one, and **every run writes its own measured fill into
`RUN_SUMMARY.fieldFillPercent`** so you can check the window you actually bought.

### The one thing you must know before buying this

**The winning supplier is named, but is NOT contactable.** There is no email and no phone for the
company that won the contract — not "sparse", *absent*. Verified three ways:

- **0 of 2,971** supplier records in a full month carry a `contactPoint` object at all
- **0 of 2,971** contain an `@` anywhere in the raw record
- the public notice HTML page adds nothing — the only address on it is the buyer's

So this Actor advertises **no** `supplierEmail` or `supplierPhone` column. A column that is null
everywhere gets cut, not shipped.

**The contactable side of an award notice is the BUYER** — the public body that just signed. That
side is 100% email, 69.8% a named individual, 51.6% direct phone. 52.6% of the addresses are
`firstname.lastname@` style; the rest are role inboxes (`procurement@`, `tenders@`,
`commercial.team@`). Top domains in the measured month: `suffolk.gov.uk` (258), `nhs.net` (189),
`leicester.gov.uk` (98), `defra.gov.uk` (83).

If you sell **into** UK government, that is the list. If you wanted contact details for the winning
vendors, this feed does not have them and neither does anything else on the site.

### What one row looks like

A real row from the verified walk:

```json
{
  "releaseId": "e22d7e44-5d3d-4479-85ad-4028d8f954c5-912492",
  "ocid": "ocds-b5fd17-687d393f-098a-40c5-a5ad-17a5de6262a6",
  "noticeUrl": "https://www.contractsfinder.service.gov.uk/Notice/e22d7e44-5d3d-4479-85ad-4028d8f954c5",
  "noticeType": "awardUpdate",
  "isAmendment": true,
  "publishedDay": "2026-08-28",
  "title": "Provision of Jira and Confluence cloud hosted software 2026",
  "cpvCode": "48000000",
  "cpvDescription": "Software package and information systems",
  "procurementCategory": "services",
  "procurementMethodDetails": "Call-off from a framework agreement",
  "deliveryRegions": ["London"],
  "awardValueGbp": 232204.08,
  "awardCurrency": "GBP",
  "awardDate": "2026-04-24",
  "contractStartDate": "2026-04-24",
  "contractEndDate": "2027-04-23",
  "contractDurationDays": 364,
  "bidDeadline": "2026-04-01",
  "smeSuitable": false,
  "buyerName": "Government Commercial Agency",
  "buyerContactName": "Greg Marsh",
  "buyerEmail": "info@gca.gov.uk",
  "buyerPhone": "07772271119",
  "buyerStreet": "39 Old Hall Street",
  "buyerLocality": "Liverpool",
  "buyerPostcode": "L3 9PP",
  "supplierCount": 1,
  "supplierName": "AUTOMATION CONSULTANTS LIMITED",
  "supplierCompanyNumber": "04047568",
  "supplierAddress": "Field Farm Barn RG7 4DA GB",
  "supplierPostcode": "RG7 4DA",
  "supplierIsSme": false
}
```

### Who buys this

- **Bid-writing and tender consultancies** — `contractEndDate` is present on 100% of rows and 90.5%
  of it is in the future. That is a dated pipeline of re-tenders, with the incumbent and the price
  already attached.
- **Vendors selling into the public sector** — filter by `cpvPrefixes` to your category, get the
  named buyer who has *just proven they have budget and signing authority* for exactly that thing.
- **SMEs hunting subcontract work** — `supplierName` plus `supplierCompanyNumber` tells you who won
  the prime; `smeSuitable` (59.4% true) tells you which frameworks were built for firms your size.
- **Market and competitor analysis** — 1,916 named suppliers a month against £5.35bn of spend, with
  CPV codes, so you can size a category or track a rival's public-sector wins.

### Filters

All filtering happens **before delivery**, so a filtered-out notice is never charged.

| input | what it does |
|---|---|
| `lastDays` | Rolling window; leave the dates empty. Default 7. |
| `publishedFrom` / `publishedTo` | Explicit window. **`publishedTo` is inclusive here** (see below). |
| `cpvPrefixes` | `["45"]` construction, `["72","48"]` IT, `["85"]` health. Matches the main and additional CPV codes. |
| `keywords` | Title or description contains any of these. |
| `buyerNameIncludes` | e.g. `NHS`, `County Council`, `Ministry of`. |
| `regions` | Delivery region — but see the honest limit below. |
| `minAwardValueGbp` / `maxAwardValueGbp` | Value band. |
| `includeAmendments` | Keep or drop the 7.0% `awardUpdate` rows. |
| `maxNotices` | Hard cap on rows delivered **and charged**. Default 1,000. |

### Honest limits

**1. `publishedTo` is exclusive at the source — this Actor fixes it.** Asking the API for
`publishedFrom=2026-09-15&publishedTo=2026-09-15` returns **0 releases** and no error, which reads
exactly like "nothing was awarded that day". This Actor sends the following day as the bound, so the
date you type is the date you get. If you call the API yourself, this will cost you a day.

**2. The source rate-limits hard, and that sets the runtime.** Contracts Finder allows roughly **12
requests per 120 seconds**, then answers `HTTP 429 — Rate limit of 12 exceeded. Please retry after
120 seconds.` with `Retry-After: 120`. Enforcement **lags the breach by tens of seconds**, so a
burst that looks fine gets blocked later. Measured: 40 requests in 6.6s all returned 200, and the
*next* request was blocked. August at 6s pacing walked in 323s but ate one 120s penalty; July at the
shipped 10s default walked **34 pages / 3,356 notices in 381s with zero 429s**. Measured throughput
is therefore about **530 rows per minute**:

| window | notices | wall-clock |
|---|---|---|
| the daily bounded run (40 notices) | 40 | **4s, measured** |
| one week | ~740 | **~80s, measured** |
| one month | 2,700–3,400 | **~6 min, measured** |
| one year | ~35,000 | ~1 hour |

The Actor honours `Retry-After` and **permanently widens its own pace after any 429** rather than
buying a second penalty a few pages later.

**3. There is no total anywhere in the feed.** The OCDS envelope publishes no count, so progress
cannot be shown, a run cannot be pre-sized, and there is nothing to floor-assert a walk against. The
only end-of-data signal is the absence of `links.next`. That is why a page that fails to arrive is
reported as a **truncation** (`RUN_SUMMARY.unreachable`, and the run is marked failed) and never as
the end of the window — a short register that reads as complete is the one bug you could not see.

**4. `ocid` is not unique.** Amendments reuse the `ocid` of the award they amend — 2,711 releases
collapse to 2,616 `ocid`s in one month. This Actor dedupes on `releaseId`. Deduping on `ocid`, which
is the obvious choice, silently drops every amendment.

**5. Pre-2017 dates are unreliable.** The archive is addressable back to 2015, but the backfill has
date drift: a `2015-03-01 → 2015-03-08` window returns records whose own release date is
`2016-11-18`. From 2018 onward the dates align exactly. Don't trust the date filter on the
2015–2016 tail.

**6. `deliveryRegions` is a filter that also filters by silence.** Only 54.5% of notices publish a
region at all, so `regions: ["London"]` also drops every notice that left the field blank.

**7. A literal `-` is not a value.** The feed publishes placeholder strings — `-`, `N/A`, `TBC` —
in real fields. On 2026-09-15, **53 of 200** buyer records had `locality: "-"`. Counted naively that
column is 100% full; it is 73.5%. Every field goes through a placeholder filter before it is
counted or delivered, which is why the numbers in this README are lower than a raw count would give.

**8. Supplier addresses contain literal newlines.** 96 of the 200 captured supplier addresses have
`\r\n` inside a single free-text field. Exported straight to CSV they break the row. They are
normalised here, and the postcode is regex-extracted (91.1% recoverable).

**9. 3.0% of awards publish a value of exactly £0** (82 of 2,711). That is the source's own figure,
not a missing value — but any `minAwardValueGbp`/`maxAwardValueGbp` filter drops rows whose value
is genuinely absent rather than treating them as zero.

### Pricing

**$0.002 per award notice delivered. No run-start fee.**

| what you want | notices | cost |
|---|---|---|
| One working day of UK awards | ~130 | **~$0.26** |
| A week (measured: 741) | ~740 | **~$1.48** |
| A full month (measured: 2,711 and 3,356) | 2,700–3,400 | **$5.42–$6.71** |
| The default capped run | 1,000 | **$2.00** |
| A full year | ~35,000 | **~$70** |
| Filtered to one CPV category for a month | usually 50–300 | **cents** |

Notices excluded by your filters are never delivered and never charged. Duplicates are dropped
before billing. A run that finds nothing costs nothing. The cap is checked against rows **produced**,
not batches flushed, so `maxNotices: 40` delivers and bills exactly 40.

### Source and licence

Data comes from the Contracts Finder OCDS search endpoint at
`contractsfinder.service.gov.uk/Published/Notices/OCDS/Search` — the same public feed anyone can
call in a browser, with no key and no account. The envelope declares its own publisher (Cabinet
Office) and licence: **Open Government Licence v3**
(`nationalarchives.gov.uk/doc/open-government-licence/version/3/`), reproduced in every
`RUN_SUMMARY`.

Buyer contact details are published by the authorities themselves as the official route for
suppliers to contact them about that procurement. They are business contact details for a public
body, not personal consumer data — but how you use them for outreach is still your responsibility
under UK GDPR and PECR.

# Actor input Schema

## `lastDays` (type: `integer`):

How many days back from today to read, when you leave the two dates below empty. 1 means today only. About 130 award notices are published on a typical UK working day.

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

First publication date to include (YYYY-MM-DD), inclusive. Set this to override 'Last N days'. Contracts Finder is addressable back to 2015, but dates before 2017 are unreliable - see the README.

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

Last publication date to include (YYYY-MM-DD), INCLUSIVE. The API itself treats this bound as exclusive; this Actor adds the day back so the date you type is the date you get. Defaults to today.

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

Keep only notices whose CPV classification starts with one of these, for example 45 (construction), 72 (IT services), 79 (business services), 85 (health and social work), 60 (transport). Leave empty for every category. Matches the main CPV code and any additional ones.

## `keywords` (type: `array`):

Keep only notices whose title or description contains at least one of these words, case-insensitive - for example 'framework', 'maintenance', 'software'.

## `buyerNameIncludes` (type: `string`):

Keep only notices from buyers whose name contains this text, for example 'NHS', 'County Council', 'Ministry of'.

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

Keep only notices delivered in these regions. NOTE: only 54.5% of award notices publish a region at all (measured over 2,711 notices), so using this filter also drops every notice that left the field blank.

## `minAwardValueGbp` (type: `integer`):

Keep only contracts awarded for at least this much. The median award is GBP 100,608. 3.0% of notices publish a value of exactly 0; any value filter drops those.

## `maxAwardValueGbp` (type: `integer`):

Keep only contracts awarded for at most this much.

## `includeAmendments` (type: `boolean`):

Contracts Finder republishes a corrected award as an 'awardUpdate'. 7.0% of notices are these (measured over a full month). Keep them on to see revised values and dates; turn them off for one row per original award.

## `maxNotices` (type: `integer`):

Cap on award notices delivered - and therefore charged. 0 means no cap. A full month of UK awards is about 2,700 notices.

## `requestDelayMs` (type: `integer`):

Contracts Finder allows about 12 requests per 120 seconds and answers HTTP 429 with a 120-second Retry-After, enforced with a lag of tens of seconds. Measured: 6000 ms hit one 429 walking a full month; 10000 ms walked a full month with none. Lower it at your own risk - the Actor backs off and permanently widens its pace after any 429.

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

Apify Proxy. RESIDENTIAL is pinned by default: each OCDS page is ~590 KB and a body that size is truncated mid-stream on the datacenter proxy while still returning HTTP 200. The run detects a short body and retries, but datacenter makes that common enough to slow a long walk down. Switch to datacenter if you prefer and accept the retries.

## Actor input object example

```json
{
  "lastDays": 7,
  "publishedFrom": "",
  "publishedTo": "",
  "cpvPrefixes": [],
  "keywords": [],
  "buyerNameIncludes": "",
  "regions": [],
  "includeAmendments": true,
  "maxNotices": 40,
  "requestDelayMs": 10000,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per award notice: the contract, its value, its CPV category, its start and end dates, the supplier that won it, and the buying authority's procurement contact, email and phone.

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

RUN\_SUMMARY: the exact window read, pages fetched, notices delivered vs filtered out vs dropped as duplicates, rate-limit waits, per-field fill measured on THIS run, and any page the cursor could not reach (which means the window is incomplete).

# 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 = {
    "lastDays": 7,
    "maxNotices": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/contracts-finder-awards-scraper").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 = {
    "lastDays": 7,
    "maxNotices": 40,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/contracts-finder-awards-scraper").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 '{
  "lastDays": 7,
  "maxNotices": 40
}' |
apify call scrapersdelight/contracts-finder-awards-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/contracts-finder-awards-scraper"
        }
    }
}
```

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/MfGl0jqMHALoRYFsf/builds/PneeX3jhxIDKUcLaq/openapi.json
