# UK Government Tenders - Find a Tender & Contracts Finder (`jpopendata/uk-public-tenders`) Actor

UK public tenders and contract awards from the official Find a Tender and Contracts Finder APIs: buyer, CPV, value, deadline, region and awarded companies. Keyword, CPV, buyer, region, value and date filters; cross-service dedupe; OGL v3. Unofficial; not affiliated with the UK Government.

- **URL**: https://apify.com/jpopendata/uk-public-tenders.md
- **Developed by:** [JP Open Data](https://apify.com/jpopendata) (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 per 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 Government Tenders - Find a Tender & Contracts Finder

**UK public tenders and contract awards from both official services in one table: buyer, CPV codes, value, deadline, region, stage and the awarded companies, one row per procurement.**

The UK publishes procurement notices on **Find a Tender** (the central platform under the Procurement Act 2023) and on **Contracts Finder**. This Actor reads the official OCDS APIs of both, compiles every notice about the same procurement into one row (so a row keeps the value from the tender notice and the supplier from the award notice), merges a procurement that appears on both services, and filters by keyword, CPV, buyer, region, value, dates and stage.

> **Unofficial tool. Not affiliated with or endorsed by the UK Government, the Cabinet Office or Crown Commercial Service.** Data: Contains public sector information licensed under the Open Government Licence v3.0. No contact names, e-mails, phone numbers or addresses are returned, and supplier names that may belong to a person are withheld (see below).

### Quick start — verified input

Run on 2026-10-04 on the Apify platform: 20 rows, 2 requests, under a minute. An empty input `{}` runs exactly this.

```json
{
  "daysBack": 7,
  "maxItems": 20
}
```

IT tenders still open for bids, over £100k, in London or the South East:

```json
{
  "keywords": ["software", "cyber security"],
  "cpvCodes": ["72000000"],
  "regions": ["London", "South East"],
  "valueMin": 100000,
  "onlyOpen": true,
  "daysBack": 30,
  "maxItems": 200,
  "maxApiRequests": 150
}
```

#### Common input mistakes

| Wrong | Right | Why |
|---|---|---|
| `"cpvCodes": ["software"]` | `"keywords": ["software"]` or `"cpvCodes": ["72000000"]` | CPV codes are digits; `72000000` matches every code under 72. |
| `"regions": ["Manchester"]` | `"regions": ["North West"]` | Regions are the 12 UK regions (or England, or codes such as UKI). |
| `"valueMin": "£50k"` via the form | `"valueMin": 50000` | Whole pounds. Via the API, "£50,000" and "50k" are accepted. |
| `"publishedFrom": "last week"` | `"daysBack": 7` | Dates are YYYY-MM-DD (UTC), DD/MM/YYYY, today or yesterday. |
| `"daysBack": 90` with the default budget | also raise `maxApiRequests` | One request returns 100 notices; a working day is about 7 requests. |

An invalid value stops the run before any request, with a message that names the field and lists the valid values.

#### Empty results?

A window over a weekend or bank holiday can hold only a handful of notices, and combined filters (keyword + CPV + region + value) can exclude everything. The run then ends successfully with 0 rows. Widen `daysBack`, drop a filter, or check `RUN_SUMMARY` in the key-value store: it shows how many notices each service returned.

### What one row contains

`title`, `buyerName`, `buyerRegion`, `stage` (planning / tender / award), `status`, `isOpenForBids`, `noticeTypes` (Find a Tender UK1–UK17), `mainCpvCode`, `cpvCodes`, `procurementCategory`, `procurementMethodDetails`, `valueAmount`, `valueMinAmount`, `valueCurrency`, `valuePlaceholder`, `tenderDeadline`, `contractStart`, `contractEnd`, `deliveryRegions`, `suitableForSme`, `suitableForVcse`, `awards` (each with status, date, value and suppliers), `awardedSuppliers`, `awardedSupplierIds` (Companies House numbers as `GB-COH-…`), `awardedValue`, `awardDate`, `sources`, `foundOnBothSources`, `ocids`, `noticeUrl`, `noticeUrls`, `firstReleaseAt`, `lastUpdatedAt`, `matchedKeywords`, plus `source`, `sourceUrl`, `license` and `retrievedAt` on every row.

A value of £0 or £1 (a placeholder some buyers type) is returned as `valueAmount: null` with `valuePlaceholder: true`.

### How it reads the services

- Both APIs are read newest first. With `maxItems`, a service stops as soon as it has that many matching procurements, so the Quick start needs one page per service. Without a small `maxItems`, the whole window is read.
- Measured on 2026-10-01 (one working day): 649 notices (Find a Tender 452, Contracts Finder 197) became 616 procurements in 7 requests and 73 seconds; 2 procurements were found on both services and merged.
- Requests go one at a time, at least 11 seconds apart, and a `429` is retried only after the wait the service asks for (`Retry-After`). Contracts Finder signals an exceeded limit with HTTP 403 and asks for a 5-minute pause; that service then stops and the run keeps what it has.
- Keywords, CPV, buyer, region, value, closing date and stage are filtered here, after reading. Neither API offers keyword search.
- If a service fails mid-run, the rows from what was read are kept and the run succeeds with `complete: false`. If nothing could be read at all, the run fails visibly.

### Personal data and supplier names

Contact points, addresses and the notice description text are never returned (buyers' free text can name people). Supplier names are shown only when they are clearly an organisation: a Companies House, charity, NHS or other organisations-only registry id, a name ending in a legal form (Ltd, Limited, PLC, LLP, CIC, GmbH …), or a public body (Council, University, NHS …). Everything else, including sole traders, is withheld as `name: null, nameWithheld: true` and counted in `suppliersWithheld`; the award value is still returned. On 2026-10-01 this withheld 111 of 667 supplier entries (17%), mostly companies whose name lacks "Ltd". Award values are not affected.

### Honest limits

- A row reflects the notices read in the window. A procurement published last month whose award appeared this week carries what the award notice says.
- Find a Tender selects by "last updated", Contracts Finder by publication date; dates are UTC days.
- Matching across services uses title + buyer and merges a pair only when it is unambiguous, so some duplicates can remain.
- Delivery regions come from Find a Tender NUTS codes and Contracts Finder region names; notices with neither are dropped by a region filter.

### Pricing

Pay per row stored in the dataset. A run stops by itself when it reaches the maximum charge you set.

# Actor input Schema

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

Optional. Words or phrases to find in the notice title or text, whole words, any case, e.g. "software", "window cleaning", "cyber security". A procurement matches if it contains ANY of them. Leave empty for all notices.

## `cpvCodes` (type: `array`):

Optional. CPV codes or prefixes, e.g. "72000000" (IT services: matches every code under 72), "45" (construction work), "79341000". Main and additional CPV codes are both checked.

## `buyers` (type: `array`):

Optional. Part of the buyer (contracting authority) name, e.g. "NHS", "Home Office", "Leeds City Council". Any match counts.

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

Optional. UK delivery regions: London, South East, South West, East of England, East Midlands, West Midlands, North East, North West, Yorkshire and the Humber, Wales, Scotland, Northern Ireland, or England (all English regions). NUTS/ITL codes such as UKI or TLI work too. If a notice names no delivery region, the buyer's region is used; notices with neither are dropped when this is set.

## `valueMin` (type: `integer`):

Optional. Minimum estimated value in pounds (the awarded value when no estimate is published), e.g. 50000. Notices without a value in GBP are dropped when a value range is set.

## `valueMax` (type: `integer`):

Optional. Maximum estimated value in pounds, e.g. 1000000.

## `daysBack` (type: `integer`):

Read notices published or updated in the last N days (1..90). Ignored when Published from is set. Both services publish several hundred notices per working day; with maxItems the run stops early once it has the newest matching rows.

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

Optional. First day of the window, YYYY-MM-DD (UTC), e.g. "2026-09-01". DD/MM/YYYY, "today" and "yesterday" also work. Find a Tender selects by the date a notice was published or last updated; Contracts Finder by its publication date.

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

Optional. Last day of the window, YYYY-MM-DD (UTC). Empty = now. The window can be at most 366 days.

## `closingFrom` (type: `string`):

Optional. Keep only notices whose tender deadline is on or after this day, YYYY-MM-DD. Notices without a published deadline are dropped when this is set.

## `closingTo` (type: `string`):

Optional. Keep only notices whose tender deadline is on or before this day, YYYY-MM-DD.

## `stages` (type: `array`):

Optional. planning (pipeline, planned procurement, early engagement), tender (open opportunities and calls for competition) and/or award (contract awards, with the awarded companies). The stage is the most advanced one reached by the notices read. Empty = all.

## `onlyOpen` (type: `boolean`):

Keep only tenders that are still open: tender stage, status active and a deadline in the future.

## `sources` (type: `array`):

find-a-tender and/or contracts-finder (fts / cf also work). Empty = both. Every row says where it was found; a procurement published on both is returned once with foundOnBothSources = true.

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

Maximum procurement rows (1..10000), newest first. You are charged per row.

## `maxApiRequests` (type: `integer`):

Hard budget of requests to the two APIs for the whole run (1..300). Requests are sent one at a time at least 11 seconds apart to stay inside the services' rate limits, so 100 requests take about 20 minutes. One request returns up to 100 notices. If the budget runs out the run still succeeds with what it read (complete=false in RUN_SUMMARY).

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

Optional and normally not needed: both APIs answer Apify datacenter IPs. If set, one sticky session is used for the whole run (never rotation).

## Actor input object example

```json
{
  "keywords": [],
  "cpvCodes": [],
  "buyers": [],
  "regions": [],
  "daysBack": 7,
  "publishedFrom": "",
  "publishedTo": "",
  "closingFrom": "",
  "closingTo": "",
  "stages": [],
  "onlyOpen": false,
  "sources": [],
  "maxItems": 20,
  "maxApiRequests": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `records` (type: `string`):

One row per UK procurement with the OGL v3 attribution (source, sourceUrl, license, retrievedAt) on every item.

# 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 = {
    "keywords": [],
    "cpvCodes": [],
    "buyers": [],
    "regions": [],
    "daysBack": 7,
    "publishedFrom": "",
    "publishedTo": "",
    "closingFrom": "",
    "closingTo": "",
    "stages": [],
    "onlyOpen": false,
    "sources": [],
    "maxItems": 20,
    "maxApiRequests": 30,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jpopendata/uk-public-tenders").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 = {
    "keywords": [],
    "cpvCodes": [],
    "buyers": [],
    "regions": [],
    "daysBack": 7,
    "publishedFrom": "",
    "publishedTo": "",
    "closingFrom": "",
    "closingTo": "",
    "stages": [],
    "onlyOpen": False,
    "sources": [],
    "maxItems": 20,
    "maxApiRequests": 30,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("jpopendata/uk-public-tenders").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 '{
  "keywords": [],
  "cpvCodes": [],
  "buyers": [],
  "regions": [],
  "daysBack": 7,
  "publishedFrom": "",
  "publishedTo": "",
  "closingFrom": "",
  "closingTo": "",
  "stages": [],
  "onlyOpen": false,
  "sources": [],
  "maxItems": 20,
  "maxApiRequests": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call jpopendata/uk-public-tenders --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jpopendata/uk-public-tenders"
        }
    }
}
```

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/crrJi1HIYRIjiU7fy/builds/0TVshMplldcfLD4zl/openapi.json
