# UK Tenders Alerts: Contracts Finder + Find a Tender (`persistent_parrot/uk-tenders-alerts`) Actor

UK tenders and awards from Contracts Finder + Find a Tender, filtered by keyword or CPV; about 3 in 10 award supplier names withheld; not an official service.

- **URL**: https://apify.com/persistent_parrot/uk-tenders-alerts.md
- **Developed by:** [Bright Path](https://apify.com/persistent_parrot) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 notice delivereds

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 Tenders Alerts: Contracts Finder + Find a Tender

Public-sector buying in the UK is announced on two government portals, and the same work is sometimes posted on both. This Actor reads both official feeds, joins the duplicates, and returns a single flat dataset of tenders and contract awards.

### What it does

- Reads Contracts Finder and Find a Tender together and returns tenders and contract awards as one flat list; a notice posted on both becomes one row.
- Narrows the list by keyword, CPV code, buyer, region, postcode, value, deadline or SME suitability.
- In monitor mode, on a schedule, returns only notices that are new or have changed since its last run.

Emails and phone numbers are removed from notice text (other text, including staff names a buyer wrote in, is kept as published), and supplier names that cannot be confirmed as organisations are withheld (about 3 in 10 supplier entries on award notices in our tests). See "Contact data and withheld supplier names" below.

### Who it is for

- Companies that sell to councils, the NHS and central government and want a standing alert for tenders in their line of work.
- Bid and proposal consultants who track opportunities across several customer industries from one scheduled run.
- Business-development and market-research teams that study contract awards: the winner, the buyer and the contract value.
- Data analysts who need a ready-made procurement table instead of parsing nested OCDS JSON.

### Input examples

Daily alert for maintenance work in the south-west, open to SMEs, closing on or after 17 October (from 00:00 UTC):

```json
{
  "mode": "monitor",
  "keywords": ["maintenance", "repairs"],
  "noticeTypes": ["tender"],
  "postcodePrefixes": ["SN", "BA", "BS"],
  "smeSuitable": true,
  "deadlineAfter": "2026-10-17",
  "maxRows": 200
}
```

Who won software work over £100k in the seven days to 17 October (a date without a time means 00:00 UTC):

```json
{
  "mode": "search",
  "dateFrom": "2026-10-10",
  "dateTo": "2026-10-17",
  "noticeTypes": ["award"],
  "cpvPrefixes": ["72"],
  "minValue": 100000,
  "includeUnknownValue": false
}
```

Quick test (the default prefill, at most 9 rows, under $0.05): `{"mode":"search","keywords":["maintenance"],"maxRows":9}`. The prefill sets only `keywords` and `maxRows`; with the input left empty, `maxRows` defaults to 1000, so raise or remove the cap for a full search.

Filters combine with AND; the values inside one filter are OR. `cpvPrefixes` are digits only (for example `45` for construction, `7226` for software services). `regions` accepts a delivery region as published or a buyer region code prefix such as `UKI`. `postcodePrefixes` ignore spaces and case. Value filters test the awarded value, or the estimated value when nothing was awarded.

### Output example

A real row from the Actor's own test run of 22 October 2026 (description shortened):

```json
{
  "source": "contractsFinder",
  "noticeId": "7eeb3a8e-8f79-46c3-92bf-a032bedc89d7",
  "ocid": "ocds-b5fd17-e12d8fe4-99cd-4d62-a6e9-ee2b1870c8f5",
  "noticeType": "tender",
  "tags": ["tender"],
  "title": "CA18576 - Electrical Maintenance. 4 Year Opportunity",
  "description": "New College Swindon invites tenders for Electrical Maintenance Services, including planned maintenance, statutory testing, reactive repairs and associated electrical works across the college estate. ...",
  "buyerName": "New College Swindon",
  "buyerId": "GB-CFS-219079",
  "buyerPostcode": "SN3 1AH",
  "deliveryRegions": [],
  "deliveryPostcode": "SN3 1AH",
  "cpvCodes": ["45310000", "50711000", "71314100"],
  "valueAmount": 400000,
  "awardedValue": null,
  "currency": "GBP",
  "deadlineDate": "2026-11-19T10:00:00Z",
  "publishedDate": "2026-10-16T14:24:18Z",
  "updatedDate": "2026-10-16T14:24:18Z",
  "status": "active",
  "procurementMethod": "open",
  "procedure": "Open procedure",
  "mainCategory": "services",
  "sme": true,
  "vcse": false,
  "suppliers": [],
  "supplierIds": [],
  "suppliersWithheld": 0,
  "url": "https://www.contractsfinder.service.gov.uk/Notice/7eeb3a8e-8f79-46c3-92bf-a032bedc89d7",
  "seenIn": ["contractsFinder"],
  "licenseUrl": "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/",
  "changeType": null
}
```

Also in each row but left out above (all `null` for this notice): `formCode`, `submissionDetails`, `buyerRegion` and `aboveThreshold`. The full field list (36 columns) is in the dataset schema. `changeType` is `new` or `changed` in monitor mode and `null` otherwise.

### How the data is built

- **Both official feeds.** Contracts Finder (the main portal for England and UK-wide opportunities) and Find a Tender (above-threshold notices, and all notices under the Procurement Act 2023) are read together. Notices posted in both places become one row, with `seenIn` showing where.
- **One flat schema.** No nested OCDS. Buyer, delivery region and postcode, CPV codes, estimated and awarded value, currency, deadline, suppliers, procedure and a direct notice link are plain columns.
- **Clean values.** All dates are UTC. Values are numbers. Missing data is `null`, not an empty string, and a value is `0` only when the notice itself says 0. Contracts Finder's award-only values and Find a Tender's contract values are both picked up.
- **Awards that are really republished.** An old award that is re-posted keeps its original `publishedDate`; `updatedDate` shows the re-post.
- **Monitor mode.** Run it on a schedule and get only notices that are new or whose content changed. A re-publish with identical content is not sent again.
- **Contact data removed.** Contact-point fields (names, emails, phones) are never read, and emails and phone numbers inside notice text are replaced. Details, and the rule for withheld supplier names, are in "Contact data and withheld supplier names" below.
- **Polite by design.** One request every 2 seconds per host, one at a time, with back-off when the service asks.

### Pricing

Pay per result: **$5 per 1,000 rows** ($0.005 per notice), charged only for rows delivered. Runs that find nothing, and runs that stop on an input error, cost nothing. Use `maxRows` to cap spend.

### Data source and licence

Notices come from the official OCDS APIs of Contracts Finder and Find a Tender, published under the [Open Government Licence v3.0](http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/). Every row carries `licenseUrl`; please keep the attribution "Contains public sector information licensed under the Open Government Licence v3.0" when you republish.

This is an independent tool, not affiliated with or endorsed by Contracts Finder, Find a Tender, the Cabinet Office or Crown Commercial Service.

### Update schedule and freshness

- No stored copy: every run reads the two official feeds live, so each run returns what the feeds list at that moment. How quickly the feeds list a notice is up to the government services.
- Search mode covers the date range you give (default: the last 24 hours, at most 31 days). All dates are UTC.
- Monitor mode is made for a daily schedule (Apify Schedules). The first run looks back `lookbackHours` (default 24); later runs continue from the previous run with a 2-hour overlap, and a re-publish with identical content is not sent again.

### Limits and good to know

- Search ranges are limited to 31 days; use monitor mode for continuous coverage.
- **Run time.** A 24-hour search takes well under a minute. Longer windows take longer: a 7-day search measured about 3 minutes. Find a Tender sometimes asks the Actor to pause for 2 minutes, and the Actor honours that, so a 31-day search can take several minutes or more. Set the run timeout accordingly (60 minutes is a safe choice for long windows). If a source keeps refusing for about half an hour, the run stops with a clear message and no charge.
- Contracts Finder returns at most 100 notices per request. The Actor follows the feed's paging cursor when it offers one, and otherwise splits the time window automatically until nothing is cut off. If Contracts Finder answers with a rate-limit refusal (HTTP 403), the Actor waits 5 minutes and retries up to twice, then stops with a clear message and no charge. In the unlikely case that a one-minute window still holds 100 notices, it is flagged as `cfWindowTruncated` in the `OUTPUT` record.
- Cross-feed merging needs the same buyer name, title and deadline date. A notice that is worded differently on the two feeds will appear twice.
- Monitor state is stored in a named key-value store in your account: one baseline per saved Task, and for direct runs one per distinct set of filters (changing any filter on a direct run starts a fresh baseline that looks back `lookbackHours`). The first run looks back `lookbackHours` (default 24); later runs continue from the previous run with a 2-hour overlap.
- The `OUTPUT` record also lists how many requests were sent to each source, how many retries happened and how long the Actor waited, so you can see how close a run is to your time limit. A notice with no buyer in the source data is skipped and counted in `rowsDroppedInvalid`.
- Monitor mode delivers the oldest notices first. If `maxRows` or your run's maximum charge cuts a run short, the next run carries on where it stopped. A monitor window never reaches back more than 31 days: if a schedule was paused longer, older notices are skipped and `windowCapped` is `true` in the `OUTPUT` record.
- If either feed is down, the run stops with a clear message and charges nothing.
- No logins. Supplier names without an organisation-register id are withheld unless they clearly look like a company, so about 3 in 10 supplier entries on award notices are shown as `[individual supplier withheld]` (count in `suppliersWithheld`).

### Contact data and withheld supplier names

Contact-point fields (names, emails, phones) are never read. Emails and phone numbers inside notice text (UK numbers starting 0, +44 or 0044, including a bracketed `(0)` or `(020)`; any `+<country code>` number, with the area code optionally in brackets, such as `+44 (20) 7946 0958` or `+1 (212) 555-0100`; and Irish `00353`) are replaced with `[email removed]` or `[phone removed]`; notice ids, CPV codes, prices and dates are kept. Other text, including staff names a buyer wrote into the notice, is kept as published. Supplier names that cannot be confirmed as organisations (this includes sole traders and some real companies) are replaced by `[individual supplier withheld]` and their ids are dropped too; a supplier is named only on a strict allow-list: either its id is a company, charity or public-body register id (Companies House, Charity Commission, OSCR, and similar), or the whole name is a plain company name ending in Ltd, Limited, LLP, PLC, CIC, CIO, LP, Inc, LLC, GmbH, S.A. or B.V., or a plain public-body name ("Leeds City Council", "... NHS Foundation Trust", "... University", "... Police Service", "... Combined Authority"; or exactly one word after NHS, University of, Ministry of, Department for/of or HM, such as "University of Leeds" or "HM Treasury"; or a short fixed list such as "HM Revenue and Customs"). A name with " of " after its second word before a council or similar body is withheld. Names with commas, brackets, dashes, a title (Mr, Dr...), "t/a", "trading as", "c/o" or a person-role word (consultant, locum, tutor...) are always withheld, as is everything else. When unsure, the name is withheld, so some real companies are hidden (for example names with brackets, unless they have a register id). In practice this affects a lot of award suppliers: supplier names without an organisation-register id are withheld unless they clearly look like a company. On recent data about 3 in 10 supplier entries were withheld (measured: 28% in one 24-hour run, 30% over 7 days). The number withheld on each row is in `suppliersWithheld`.

### FAQ

**Does it cover both Contracts Finder and Find a Tender?** Yes, both by default. Use `sources` to read only one.

**How do I get only new notices every day?** Use `"mode": "monitor"` and put the Actor on a daily Apify schedule. It returns notices that are new or whose content changed; `changeType` says which.

**Why do some suppliers show as `[individual supplier withheld]`?** A supplier name is shown only when it carries an organisation-register id or clearly looks like a company or public body. Anything else is withheld, including some real companies. About 3 in 10 supplier entries on award notices were withheld in our tests; the number per row is in `suppliersWithheld`.

**Why does the same notice sometimes appear twice?** Notices from the two feeds are merged only when buyer name, title and deadline date match. If the two feeds word them differently, you get two rows.

**How much does a first test cost?** The default prefill returns at most 9 rows, which is under $0.05 at $0.005 per notice.

**Can I use the data commercially?** The notices are published under the Open Government Licence v3.0, which asks for attribution. See "Data source and licence".

**Something looks wrong. Where do I report it?** Open an issue on this Actor's page with the run link and the `noticeId`. We aim to reply within 24 hours.

### Changelog

- **22 Oct 2026, first release.** Both portals joined into a single dataset; search and monitor modes. Both feeds are queried in UK local time, including the night the clocks change (25 Oct 2026). Notices that list their buyer only as a party with the role buyer still get a buyer; a notice with no buyer at all is skipped and counted in `rowsDroppedInvalid`. The `OUTPUT` record reports requests, retries and waiting time per run.

# Actor input Schema

## `mode` (type: `string`):

Search returns every notice published in the date range that passes your filters. Monitor remembers what it already sent (per task) and returns only new or changed notices; schedule it daily.

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

Which official feeds to read. Notices posted on both are merged into one row.

## `dateFrom` (type: `string`):

ISO date or datetime, UTC (e.g. 2026-10-16 or 2026-10-16T09:00:00Z). A date without a time means 00:00 UTC. Default: 24 hours ago. The range may span at most 31 days.

## `dateTo` (type: `string`):

ISO date or datetime, UTC. A date without a time means 00:00 UTC, so 2026-10-17 stops at the start of that day; use 2026-10-18 to include all of 17 October. Default: now.

## `lookbackHours` (type: `integer`):

How far back the first monitor run looks. Later runs continue from the previous run (with a 2-hour overlap).

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

Keeps a notice when any of these words appears in its title or description (upper and lower case count the same). Max 50. Empty = no keyword filter.

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

Digits only, 2 to 8 long, e.g. 45 (construction) or 7226 (software services). Keeps notices with any CPV code starting with one of them.

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

Keep notices whose buyer name contains any of these (case-insensitive), e.g. NHS, Council.

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

Delivery region as published (e.g. East of England) or a buyer region code prefix (e.g. UKI for London).

## `postcodePrefixes` (type: `array`):

e.g. SN, SW1, M1. Matched against the delivery postcode and the buyer postcode, ignoring spaces and case.

## `noticeTypes` (type: `array`):

Empty = all types. Use tender for open opportunities and award for results.

## `deadlineAfter` (type: `string`):

ISO date. Keeps notices whose submission deadline is later than this date (00:00 UTC). Notices without a deadline are dropped when this is set.

## `minValue` (type: `number`):

Tested on the awarded value, or the estimated value if there is no award.

## `maxValue` (type: `number`):

Tested on the awarded value, or the estimated value if there is no award.

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

When a minimum or maximum value is set, keep notices that publish no value.

## `smeSuitable` (type: `boolean`):

Keep only notices flagged as suitable for small and medium enterprises.

## `vcseSuitable` (type: `boolean`):

Keep only notices flagged as suitable for voluntary, community and social enterprises.

## `maxRows` (type: `integer`):

Stop after this many result rows (cost control).

## Actor input object example

```json
{
  "mode": "search",
  "sources": [
    "contractsFinder",
    "findATender"
  ],
  "lookbackHours": 24,
  "keywords": [
    "maintenance"
  ],
  "includeUnknownValue": true,
  "smeSuitable": false,
  "vcseSuitable": false,
  "maxRows": 9
}
```

# Actor output Schema

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

All matching tender and award notices (one row per notice).

## `summary` (type: `string`):

Run summary (requests, retries, rows delivered and charged, failed sources).

# 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": [
        "maintenance"
    ],
    "maxRows": 9
};

// Run the Actor and wait for it to finish
const run = await client.actor("persistent_parrot/uk-tenders-alerts").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": ["maintenance"],
    "maxRows": 9,
}

# Run the Actor and wait for it to finish
run = client.actor("persistent_parrot/uk-tenders-alerts").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": [
    "maintenance"
  ],
  "maxRows": 9
}' |
apify call persistent_parrot/uk-tenders-alerts --silent --output-dataset

```

## MCP server setup

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

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/BaODsNRG8fooO2bEh/builds/YMe5s88nhHmnqvQOd/openapi.json
