# UK Public Sector Tenders (Contracts Finder) – OCDS (`signalrows/tenders-uk-contracts-finder`) Actor

Open tenders, contract awards and pipeline notices from the UK government's Contracts Finder, via its official OCDS API: buyer, CPV categories, value, deadline, region and documents. Open Government Licence data; contact details masked by default.

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

## Pricing

from $4.00 / 1,000 record extracteds

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 Sector Tenders (Contracts Finder) – OCDS

Get **UK government tenders, contract awards and procurement pipeline notices** from **Contracts Finder** (the UK government's official notice service for public sector contracts over £12,000, run by the Cabinet Office / Crown Commercial Service) as clean, structured data: **buyer**, **CPV categories**, **estimated value (GBP)**, **submission deadline**, **region / postcode**, procurement method, documents and the notice URL. The actor uses the **official OCDS API** (Open Contracting Data Standard 1.1) — no scraping — and the data is published under the **Open Government Licence v3.0**, which allows commercial reuse with attribution. Contact details of buyer staff are masked by default. Runs in one click with sensible defaults (open tenders published in the last 7 days); `monitor` mode returns only what changed since the last run.

### What you get

One record per contracting process (OCDS `ocid`), in the same `TenderRecord` schema as the other tender actors of this studio, plus Contracts Finder-specific fields.

| Field | Type | Example |
|---|---|---|
| `source` | string | `"uk-contracts-finder"` |
| `sourceId` / `ocid` | string | `"ocds-b5fd17-0ef77028-82a3-4664-830f-1408fc3afa4e"` (stable across tender → amendment → award) |
| `noticeId` / `releaseId` | string | Contracts Finder notice id / OCDS release id |
| `url` | url | `https://www.contractsfinder.service.gov.uk/Notice/2fc5a2e0-854f-43ec-b88b-0eafbb58d740` |
| `title`, `titleEn` | string | `"Contract for the Provision of Hire Vehicles"` |
| `description` | string/null | ≤600-char excerpt, e-mails/phones redacted |
| `buyer` | object | `{ "name": "Merseyside Waste Disposal Authority", "unit": null, "type": "authority" }` |
| `buyerName` | string | flat copy of `buyer.name` for tables |
| `buyer.type` | enum | `municipality` (councils) · `hospital` (NHS) · `ministry` (central government) · `authority` (agencies, police, regulators) · `gov-company` (Ltd/plc buyers) · `other` (schools, universities, charities) |
| `stage` / `releaseTag` | enum / string\[] | `tender` · `award` · `planning` / e.g. `["tenderAmendment"]` |
| `tenderType` | enum | `public` (open/restricted competition) · `framework` (framework or DPS call-off) · `exemption` (limited/direct award) · `rfi` (planning / early engagement) · `other` |
| `tenderNumber` | string/null | buyer's reference, e.g. `"MT238218"` |
| `status` | enum | `open` · `closed` · `awarded` · `cancelled` · `unknown` (deadline vs. now; award notices are `awarded`) |
| `publishedAt`, `updatedAt` | ISO datetime | notice publication / this release's date |
| `deadlineAt` | ISO datetime/null | `tender.tenderPeriod.endDate`, e.g. `"2026-10-26T12:00:00Z"` |
| `valueGBP`, `minValueGBP` | number/null | estimated (or awarded) value in GBP |
| `categories` / `cpvCodes` | string\[] | `["34100000 Motor vehicles"]` / `["34100000"]` |
| `procurementMethod`, `procurementMethodDetails` | string/null | `"open"` / `"Open procedure (below threshold)"` |
| `mainProcurementCategory` | string/null | `goods` · `services` · `works` |
| `suitableForSme`, `suitableForVcse` | boolean/null | Contracts Finder suitability flags |
| `location` | object | `{ "city": "Liverpool", "region": "North West" }` (published region, else derived from the postcode area) |
| `deliveryRegions`, `deliveryCountries`, `postcode`, `postcodeArea` | | as published in `items[].deliveryAddresses[]` |
| `contractPeriod` | object | `{ "startDate": "2026-12-01T00:00:00Z", "endDate": "2029-11-30T23:59:59Z" }` |
| `documents` | array | `[{ "title": "Opportunity notice on Contracts Finder", "url": "…" }, { "title": "Bidding documents", "url": "…" }]` |
| `awards` | array | award notices: `[{ "supplierName": "D+B FACADES UK LTD", "supplierId": "GB-CFS-338591", "supplierMasked": false, "valueGBP": 714000, "date": "2026-05-16T00:00:00+01:00", "status": "active" }]` |
| `contact` | object/null | `{ "name": null, "email": null, "role": "Procurement Support", "unit": null }` (masked; generic role kept) |
| `changeType` | enum | `new` · `updated` · `deadline-changed` · `awarded` · `closed` · `unchanged` (monitor mode) |
| `source_url` | url | the notice page |
| `fetched_at` | ISO datetime | `2026-09-24T17:12:03.101Z` |
| `actor_version` | string | `0.1` |

```json
{
  "source": "uk-contracts-finder",
  "sourceId": "ocds-b5fd17-0ef77028-82a3-4664-830f-1408fc3afa4e",
  "url": "https://www.contractsfinder.service.gov.uk/Notice/2fc5a2e0-854f-43ec-b88b-0eafbb58d740",
  "title": "Contract for the Provision of Hire Vehicles",
  "titleEn": "Contract for the Provision of Hire Vehicles",
  "buyer": { "name": "Merseyside Waste Disposal Authority", "unit": null, "type": "authority" },
  "buyerName": "Merseyside Waste Disposal Authority",
  "tenderType": "public",
  "tenderNumber": "MT238218",
  "status": "open",
  "publishedAt": "2026-09-24T17:45:10+01:00",
  "updatedAt": "2026-09-24T17:45:10+01:00",
  "deadlineAt": "2026-10-26T12:00:00Z",
  "categories": ["34100000 Motor vehicles"],
  "cpvCodes": ["34100000"],
  "location": { "city": "Liverpool", "region": "North West" },
  "estimatedValueILS": null,
  "valueGBP": 48600,
  "minValueGBP": null,
  "procurementMethod": "open",
  "procurementMethodDetails": "Open procedure (below threshold)",
  "mainProcurementCategory": "services",
  "suitableForSme": true,
  "suitableForVcse": false,
  "contractPeriod": { "startDate": "2026-12-01T00:00:00Z", "endDate": "2029-11-30T23:59:59Z" },
  "deliveryRegions": ["North West"],
  "deliveryCountries": ["United Kingdom"],
  "postcode": "L3 1BP",
  "postcodeArea": "L",
  "documents": [{ "title": "Opportunity notice on Contracts Finder", "url": "https://www.contractsfinder.service.gov.uk/Notice/2fc5a2e0-854f-43ec-b88b-0eafbb58d740" }],
  "awards": [],
  "contact": { "name": null, "email": null, "role": "Chief Executive", "unit": null },
  "contactTelephone": null,
  "ocid": "ocds-b5fd17-0ef77028-82a3-4664-830f-1408fc3afa4e",
  "noticeId": "2fc5a2e0-854f-43ec-b88b-0eafbb58d740",
  "releaseId": "2fc5a2e0-854f-43ec-b88b-0eafbb58d740-915418",
  "releaseTag": ["tender"],
  "stage": "tender",
  "changeType": "new",
  "source_url": "https://www.contractsfinder.service.gov.uk/Notice/2fc5a2e0-854f-43ec-b88b-0eafbb58d740",
  "fetched_at": "2026-09-24T17:12:03.101Z",
  "actor_version": "0.1"
}
```

### Who uses it

- **Suppliers, SMEs and bid teams** watching for new public sector opportunities in their CPV categories, regions or value bands (facilities management, construction, IT, consultancy, social care, …) with the submission deadline in one field.
- **Bid aggregators, tender portals and procurement analysts** who need Contracts Finder notices alongside other national sources in one normalized schema (`tenderType`, `buyer.type`, `deadlineAt`, `valueGBP`).
- **Market intelligence and sales teams** tracking contract awards (`stages: ["award"]`): who won what, for how much, from which council, NHS trust or department.
- **AI agents (MCP) and workflow tools** (Make, Zapier, n8n): `mode=monitor` on a daily schedule gives a clean feed of new, amended, closed and awarded notices.

### Pricing

Pay per result: **$0.004 per notice** (event `record-extracted`). `run-started` is free. No charge for runs with no matching notices. Set `maxItems` to cap your spend. In `monitor` mode you pay only for changed records.

### Input

| Parameter | Default | Notes |
|---|---|---|
| `query` | `""` | Text filter on title/description (client-side substring) |
| `stages` | `["tender"]` | Any of `tender`, `award`, `planning` |
| `publishedFrom` | last 7 days | `YYYY-MM-DD`; server-side filter on the Contracts Finder publication date |
| `publishedTo` | now | `YYYY-MM-DD` |
| `openOnly` | `true` | Keep only notices with a future deadline (planning notices count as open). Turn off for awards |
| `regions` | `[]` | Region names (`"London"`, `"Scotland"`, `"North West"`) or postcode areas (`"SW"`, `"M"`, `"EH"`) |
| `buyers` | `[]` | Buyer name substrings (`"NHS"`, `"Ministry of Defence"`) |
| `minValueGBP`, `maxValueGBP` | – | GBP bounds; notices without a published value are dropped when set |
| `mode` | `search` | `monitor` = only changes vs. the previous run's `STATE` |
| `maxItems` | 100 | Max 2000 |
| `maskContacts` | `true` | Contact name/e-mail/phone removed, text redacted, individual-looking supplier names masked, raw release dropped |
| `proxy` | none | Contracts Finder works without a proxy |
| `maxPages` | 25 | Advanced: safety cap on 100-notice API pages |
| `stateStore` | – | Advanced: named key-value store for monitor `STATE` (persists across runs) |

### Monitor mode

`mode=monitor` fetches every notice in the window (`publishedFrom`/`publishedTo`, `stages`, `query`, `regions`, `buyers`, value bounds), compares each contracting process (`ocid`) with the `STATE` saved by the previous run (fingerprint of title, status, deadline, release date, documents) and pushes only records whose `changeType` is `new`, `updated`, `deadline-changed`, `awarded` or `closed`. Include `award` in `stages` to get `awarded` transitions for tenders you saw earlier; `openOnly` narrows new/updated records to open ones, while closures and awards are always reported. Schedule it daily; the key-value store key `RUN_SUMMARY` holds the counts. On the Apify platform the run's default key-value store is per run, so set `stateStore` (e.g. `uk-contracts-finder-state`) or run from a saved task to keep state between runs.

### Use from code, Make/Zapier/n8n, or AI agents (MCP)

```bash
curl -X POST "https://api.apify.com/v2/acts/signalrows~tenders-uk-contracts-finder/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"stages": ["tender"], "regions": ["London"], "minValueGBP": 100000, "maxItems": 50}'
```

This actor is also exposed as a tool on the Apify MCP server, so AI agents can call it directly (e.g. "list open UK council tenders for cleaning services above £100k published this week").

### Limits and fair use

- Source: the official Contracts Finder OCDS Search API (`/Published/Notices/OCDS/Search`, 100 notices per request, cursor pagination), ≤1 request/second, one request at a time, identifying User-Agent `Signalrows/0.1` and `From:` header. Respects `robots.txt` and `Retry-After`.
- The API filters by publication date and stage only; `query`, `regions`, `buyers` and value bounds are applied client-side, so a narrow filter over a long window can need many pages — use `publishedFrom` and `maxPages` to bound a run.
- Contracts Finder covers England-wide and cross-UK notices published by central government, councils, NHS bodies and other public buyers above £12,000 (central government) / £30,000 (wider public sector). Above-threshold notices are also on Find a Tender (a separate actor). Scotland, Wales and Northern Ireland have their own national portals.
- `location.region` is the region published by the buyer or, if absent, an approximation from the postcode area. `buyer.type` is a heuristic on the buyer's name.
- Deadlines and values are as published in the notice; always check the notice page and the buyer's portal before submitting a bid. Attached documents are linked (`documents[]`), not downloaded.
- Collects only publicly available data. Contact details of buyer staff are personal data outside the Open Government Licence and are masked unless you set `maskContacts=false` for a declared, lawful purpose. Supplier names on award notices are kept when they are clearly organisations; names that may be individuals (sole traders) are masked (`supplierMasked: true`).
- Source structure changes are monitored daily and fixed automatically; if a run fails, please open an issue.

### Licence and attribution

Notice data is published by the Cabinet Office under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) (the OCDS package's `license` field). You may copy, publish, adapt and exploit it commercially and non-commercially, provided you acknowledge the source. Include this statement (or a link to it) in your product:

> Contains public sector information licensed under the Open Government Licence v3.0.

Source: [Contracts Finder](https://www.contractsfinder.service.gov.uk/) (Crown Commercial Service / Cabinet Office). The licence does not grant any right to suggest official status or endorsement.

### Data removal / contact

If you represent the source or a buyer and want content excluded, contact signalrows.data@gmail.com. Requests are handled within 24 hours.

### Development

- Shared schema/normalizer/masking/diff code lives in `ventures-src/apify/shared/tenders` and is vendored into `src/shared/tenders`; run `scripts\sync-shared.cmd` (or `npm run sync-shared`) after changing the shared module — never edit the vendored copy.
- `npm test` runs offline tests on redacted OCDS fixtures (`test/fixtures`). `LIVE=1 npm run test:live` performs a ≤2-request smoke test against the live API.
- `apify run` (input in `storage/key_value_stores/default/INPUT.json`) writes records to `storage/datasets/default` and `RUN_SUMMARY` to the default key-value store.

# Actor input Schema

## `query` (type: `string`):

Optional. Keep only notices whose title or description contains this text (case-insensitive). Applied client-side; the OCDS API has no text search, so a narrow query can need more pages.

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

Which OCDS stages to fetch: tender (open opportunities, incl. amendments), award (contract award notices), planning (early engagement / future opportunities). Default: tender.

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

Optional ISO date (YYYY-MM-DD). Notices published on Contracts Finder from this date. Default: the last 7 days.

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

Optional ISO date (YYYY-MM-DD). Notices published up to this date (inclusive). Default: now.

## `openOnly` (type: `boolean`):

Keep only notices that are still open: tender deadline (tenderPeriod.endDate) in the future, or planning notices without a past deadline. Turn off for award notices and closed tenders.

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

Optional. Keep only notices delivered in these regions (substring match, e.g. "London", "North West", "Scotland") or postcode areas (e.g. "SW", "M", "EH"). Empty = all of the UK.

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

Optional. Buyer (contracting authority) names, substring match, e.g. "NHS", "Borough of Haringey", "Ministry of Defence". Empty = all buyers.

## `minValueGBP` (type: `integer`):

Optional. Keep only notices with an estimated/awarded value of at least this many pounds. Notices without a published value are dropped when this is set.

## `maxValueGBP` (type: `integer`):

Optional. Keep only notices with an estimated/awarded value of at most this many pounds. Notices without a published value are dropped when this is set.

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

search: return matching notices. monitor: fetch the whole window, compare with the previous run's STATE (key-value store) and return only new/changed/awarded/closed notices with a changeType.

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

Stop after this many records. You are charged per record extracted.

## `maskContacts` (type: `boolean`):

Default on: contact names, e-mails and phone numbers of buyer staff are removed (contact.name/email = null; role kept when it is a generic role such as "Procurement Team"), e-mails/phones redacted in text, supplier names that may be individuals masked, and the raw OCDS release dropped. Personal data is not covered by the Open Government Licence; turn off only with a declared, lawful purpose.

## `proxy` (type: `object`):

Optional. Contracts Finder is reachable without a proxy; use Apify Proxy only if your runs are blocked.

## `maxPages` (type: `integer`):

Safety cap on OCDS API pages (100 notices each) fetched per run. 25 pages cover 2,500 notices, i.e. more than the maximum maxItems.

## `stateStore` (type: `string`):

Optional named key-value store for monitor STATE so it persists across runs (the run's default store is per run on the platform). Example: uk-contracts-finder-state

## Actor input object example

```json
{
  "query": "",
  "stages": [
    "tender"
  ],
  "openOnly": true,
  "regions": [],
  "buyers": [],
  "mode": "search",
  "maxItems": 100,
  "maskContacts": true,
  "proxy": {
    "useApifyProxy": false
  },
  "maxPages": 25
}
```

# Actor output Schema

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

No description

# 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 = {
    "stages": [
        "tender"
    ],
    "regions": [],
    "buyers": [],
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("signalrows/tenders-uk-contracts-finder").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 = {
    "stages": ["tender"],
    "regions": [],
    "buyers": [],
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("signalrows/tenders-uk-contracts-finder").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 '{
  "stages": [
    "tender"
  ],
  "regions": [],
  "buyers": [],
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call signalrows/tenders-uk-contracts-finder --silent --output-dataset

```

## MCP server setup

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

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/z7zpDyor9t8KAlwQZ/builds/cvhZSEj8xOAHodL1B/openapi.json
