# UK Tenders Scraper: Contracts Finder + Find a Tender (`civitab/uk-tenders-ocds`) Actor

UK public sector tenders, contract awards and pipeline notices from Contracts Finder and Find a Tender in one de-duplicated table. Filter by stage, keyword, CPV, value, region and buyer, or schedule it for new notices only. Public OCDS APIs, Open Government Licence, no login, no personal data.

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

## Pricing

$2.50 / 1,000 notices

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

*Published by **Civitab**. Apify Store ID: `civitab/uk-tenders-ocds`.*

Get UK public sector **tenders, contract awards and pipeline notices** from **Contracts Finder** and **Find a Tender** in one clean, consistent table. The Actor reads both services' official **OCDS APIs** (Open Contracting Data Standard), so there is no browser, no login and no proxy involved. The data is published under the **Open Government Licence v3.0**.

### What does UK Tenders Scraper do?

- Queries the official **Contracts Finder** OCDS search API and the official **Find a Tender** OCDS API for any date window you choose.
- Returns **one row per published notice** with the same fields for both sources: title, description, buyer, CPV codes and descriptions, value range, currency, deadline, regions (names and NUTS codes), delivery postcodes, awarded suppliers (with Companies House numbers where published), SME/VCSE suitability flags, document links and a link to the official notice page.
- Lets you **filter** by notice stage (planning, tender, award), keywords, CPV code prefixes, minimum/maximum value, region and buyer name.
- **Monitor mode** (`onlyNew`): schedule it daily and each run returns only notices it has not already delivered to you for the same filters.
- **Raw mode**: get the full OCDS release instead of the flat table, with personal contact data removed.
- **Removes personal data**: contact names, e-mail addresses and phone numbers are stripped, including from free-text descriptions and raw mode. Organisation street addresses are reduced to region and country. Organisation names and Companies House numbers are kept.

### Why use it?

- **Two sources, one schema.** Contracts Finder and Find a Tender publish overlapping but different notices. This Actor merges them so you don't maintain two integrations.
- **Stage filtering you can trust.** Stage filtering is done on each release's own tags rather than through the APIs' `stages` parameter. In the developer's tests on 22 Oct 2026, Find a Tender's `stages=tender` returned only 10 of the 67 tender notices published that day, and a comma-separated stage list returned none.
- **Resilient.** Each API page is retried up to 6 times with exponential backoff, and `Retry-After` is honoured on HTTP 429/5xx. One failed page does not end the run, and every run writes a summary with the request success rate and any failed pages.
- **Cost control.** You pay per notice saved. `maxItems` and Apify's "Max cost per run" both cap spend.

### Use cases

- **Daily tender alerts** for your CPV codes, region or keywords, delivered to e-mail, Slack or a spreadsheet through Apify integrations.
- **Sales prospecting**: find buyers that regularly procure what you sell.
- **Competitor tracking**: see which suppliers win which contracts, from which buyers and for what stated value (award notices).
- **Pipeline planning**: watch planning/pipeline notices to see upcoming procurements before they open.
- **Market sizing and research**: export weeks or months of notices for analysis.

### Who is it for?

- **Bid hunters and bid managers** who need a reliable daily feed of new UK opportunities.
- **Contractors and suppliers** of any size, including SMEs looking for notices flagged as SME-suitable.
- **Govcon / public procurement consultants** who monitor several sectors or clients at once.
- **Market researchers and analysts** studying UK public spending, buyers and suppliers.

### How to use it

1. Open the Actor in Apify Console and go to the **Input** tab.
2. Choose **Sources**: Contracts Finder, Find a Tender, or both (default: both).
3. Choose **Notice stages**: Tender (open opportunities) is selected by default. Add Planning and/or Award if you want pipeline or awarded contracts.
4. Set the date window: **Days back** (default 3), or a fixed **Date from** / **Date to**.
5. Optionally narrow results with **Keywords**, **CPV code prefixes**, **Min/Max value (GBP)**, **Region** and **Buyer name contains**.
6. Set **Max results** to cap the number of notices (and the cost) per run.
7. Click **Start**. Results appear in the **Output** tab, with an *Overview* table and an *Awards & suppliers* view.
8. For alerts, turn on **Only new notices (monitor mode)** and add a **Schedule** (for example, daily at 07:00).

### Input example

IT and software tenders over £100,000 published in the last 7 days, from both sources:

```json
{
  "sources": ["CF", "FTS"],
  "stages": ["tender"],
  "daysBack": 7,
  "cpvCodes": ["72", "48"],
  "minValue": 100000,
  "maxItems": 500
}
```

Daily monitor for NHS award notices in London:

```json
{
  "stages": ["award"],
  "daysBack": 2,
  "buyerName": "NHS",
  "region": "London",
  "onlyNew": true,
  "maxItems": 200
}
```

#### Input fields

| Field | Default | Description |
|---|---|---|
| `sources` | `["CF","FTS"]` | `CF` = Contracts Finder, `FTS` = Find a Tender |
| `stages` | `["tender"]` | `planning`, `tender`, `award`. Contract and implementation releases count as `award`. |
| `daysBack` | `3` | Look-back window in days (1-3650). Ignored if `dateFrom` is set. |
| `dateFrom` / `dateTo` | – | `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS` (UTC). Contracts Finder filters by published date, Find a Tender by last-updated date. `dateTo` defaults to now. |
| `keywords` | `[]` | Keep notices whose title or description contains ANY keyword (case-insensitive). |
| `cpvCodes` | – | CPV code prefixes, e.g. `72` (IT services), `45` (construction work), `45000000`. |
| `minValue` / `maxValue` | – | GBP. A notice is kept if its stated value range overlaps your range. Notices without any stated value are excluded when a value filter is set. |
| `region` | – | Exact region name (e.g. `London`, `Scotland`, `North East`, `East of England`) or a NUTS code (e.g. `UKM`, `UKI3`; includes the codes under it). Separate several with commas. |
| `buyerName` | – | Substring of the buyer name, e.g. `NHS`, `Council`, `Ministry of Defence`. |
| `maxItems` | `50` | Maximum notices per run (1-1,000,000). Caps cost. |
| `outputMode` | `flat` | `flat` = one clean row per notice; `raw` = full OCDS release with personal data removed. |
| `onlyNew` | `false` | Skip notices already delivered to you in earlier runs with the same filters. |
| `maxPagesPerSource` | `200` | Safety cap on API pages per source (100 releases per page). |
| `requestDelaySecs` | `1.0` | Delay between API calls (0-10 s). |

### Output example

Two real records, trimmed. The first is a Find a Tender open tender from a local run on 22 Oct 2026; the second is a Find a Tender award notice taken from saved API responses (20 Oct 2026). Descriptions are shortened.

```json
[
  {
    "ocid": "ocds-h6vhtk-078e63",
    "releaseId": "100217-2026",
    "noticeId": "100217-2026",
    "source": "FTS",
    "stage": "tender",
    "tags": ["tender"],
    "title": "DCC Wastewater Sludge Cake Haulage",
    "description": "Dŵr Cymru Welsh Water (DCWW) is seeking suitably qualified and experienced suppliers to provide wastewater sludge cake haulage services across four Lots: ...",
    "status": "active",
    "procurementMethod": "Competitive flexible procedure",
    "mainCategory": "services",
    "cpvCodes": ["90513700"],
    "cpvDescriptions": ["Sludge transport services"],
    "buyerOrg": "Dwr Cymru Cyfyngedig",
    "buyerId": "GB-COH-02366777",
    "regions": [],
    "regionCodes": [],
    "deliveryPostcodes": [],
    "valueLow": 1000000.0,
    "valueHigh": 6000000.0,
    "currency": "GBP",
    "publishedDate": "2026-10-22T21:55:44+01:00",
    "deadline": "2026-11-18T23:59:00+00:00",
    "awardDate": null,
    "suppliers": [],
    "smeSuitable": null,
    "vcseSuitable": null,
    "noticeUrl": "https://www.find-tender.service.gov.uk/Notice/100217-2026",
    "license": "Contains public sector information licensed under the Open Government Licence v3.0.",
    "scrapedAt": "2026-10-23T02:03:54+00:00"
  },
  {
    "ocid": "ocds-h6vhtk-078c51",
    "releaseId": "099219-2026",
    "source": "FTS",
    "stage": "award",
    "tags": ["award", "contract"],
    "title": "Enabling Works - Kettering General Hospital",
    "status": "complete",
    "procurementMethod": "Below threshold - limited competition",
    "mainCategory": "works",
    "cpvCodes": ["45000000"],
    "cpvDescriptions": ["Construction work"],
    "buyerOrg": "West Northamptonshire Council",
    "regionCodes": ["UKC", "UKD", "UKE", "UKF", "UKG", "UKH", "UKI", "UKJ", "UKK"],
    "valueLow": 28999.22,
    "valueHigh": 28999.22,
    "currency": "GBP",
    "publishedDate": "2026-10-20T17:58:56+01:00",
    "awardDate": "2026-09-23T00:00:00+01:00",
    "suppliers": [
      { "name": "SEMILONG SERVICES LIMITED", "companiesHouseNumber": "06842550", "scale": "sme" }
    ],
    "noticeUrl": "https://www.find-tender.service.gov.uk/Notice/099219-2026"
  }
]
```

Contracts Finder notices often also carry `deliveryPostcodes` (the work location) and the `smeSuitable` / `vcseSuitable` flags. For example, a Ministry of Defence demolition tender from the same saved data has `"deliveryPostcodes": ["B37 7ES"]`, `"smeSuitable": true`, `"vcseSuitable": false`.

Each run also writes an `OUTPUT` record to the run's key-value store with items per source, the stop reason, releases scanned, duplicates, filtered and already-seen counts, `requestSuccessRatePct`, retries, failed pages, HTTP status counts and the charged events.

### Output fields (flat mode)

| Field | Type | Description |
|---|---|---|
| `ocid` | string | OCDS procurement ID. One procurement can have several notices (e.g. tender, then award). |
| `releaseId` | string | ID of this specific published release (notice). |
| `noticeId` | string | Notice ID used in the official notice URL. |
| `source` | string | `CF` (Contracts Finder) or `FTS` (Find a Tender). |
| `stage` | string | `planning`, `tender` or `award` (contract/implementation releases are reported as `award`). |
| `tags` | array | Original OCDS release tags. |
| `title` | string | null | Notice title. |
| `description` | string | null | Notice description (e-mails and phone numbers redacted). |
| `status` | string | null | Tender status as published (e.g. `active`, `complete`). |
| `procurementMethod` | string | null | Procurement method details as published. |
| `mainCategory` | string | null | `goods`, `services` or `works`, when given. |
| `cpvCodes` | array | CPV codes found on the notice. |
| `cpvDescriptions` | array | Descriptions matching `cpvCodes`. |
| `buyerOrg` | string | null | Buying organisation name. |
| `buyerId` | string | null | Buyer identifier as published (e.g. `GB-COH-...`). |
| `regions` | array | Delivery regions; NUTS1 codes are converted to names (e.g. `UKI` → `London`). |
| `regionCodes` | array | Delivery region values as published (NUTS codes on Find a Tender). |
| `deliveryPostcodes` | array | Postcodes of the work location, when published. |
| `valueLow` / `valueHigh` | number | null | Lowest and highest positive amounts stated on the notice (tender, lots, awards, contracts). |
| `currency` | string | null | Currency of the stated values (usually `GBP`). |
| `publishedDate` | string | null | Date the notice was published. |
| `releaseDate` | string | null | Date of this release. |
| `deadline` | string | null | Tender period end date (submission deadline), when given. |
| `awardDate` | string | null | Earliest award or contract-signed date, for award notices. |
| `suppliers` | array | Awarded suppliers: `name`, `companiesHouseNumber`, `scale` (e.g. `sme`, `large`). |
| `smeSuitable` / `vcseSuitable` | boolean | null | Suitability flags, mainly on Contracts Finder. |
| `documents` | array | Document links: `type`, `description`, `url`. |
| `noticeUrl` | string | null | Link to the official notice page. |
| `license` | string | Open Government Licence attribution. |
| `scrapedAt` | string | When the record was produced (UTC). |

### Pricing

This Actor uses **pay-per-event** pricing: **$0.0025 per notice** saved to your dataset (event `notice`). That is **$2.50 per 1,000 notices**.

- You pay only for notices that land in your dataset. Notices removed by your filters, duplicates and (in monitor mode) notices you already received are not charged.
- `raw` mode costs the same per notice.
- Apify's standard synthetic "Actor start" event ($0.00005 per run) also applies.
- `maxItems` and Apify's **Max cost per run** both cap spend. When the budget is reached, the Actor stops cleanly and tells you in the status message.

Worked examples:

| What you run | Notices | Cost |
|---|---|---|
| Default run (`maxItems` 50) | 50 | $0.125 |
| A filtered daily alert that returns 20 notices | 20 | $0.05 |
| 1,000 notices | 1,000 | $2.50 |
| 7 days, all stages, both sources (3,133 notices in the developer's test run) | 3,133 | about $7.83 |
| 30 days, all stages, both sources (12,334 notices in the developer's test run) | 12,334 | about $30.84 |

A daily monitor that returns 20 new notices a day costs about $1.50 per month (30 × $0.05).

### Integrations and export

Like any Apify Actor, you can:

- **Export** results from the dataset as JSON, CSV, Excel, XML or HTML.
- **Use the Apify API** to start runs and fetch results from your own code.
- **Schedule** runs (e.g. daily) in Apify Console. Combine with `onlyNew` for alerts.
- **Trigger webhooks** when a run finishes, and connect results to tools such as Google Sheets, Slack, Zapier or Make through Apify integrations.

### Limitations and data source notes

- **Data window.** Contracts Finder is filtered by *published* date, Find a Tender by *last-updated* date, so a Find a Tender notice published earlier but updated in your window can appear.
- **Missing values.** Some notices have no value, region, deadline or CPV code because the buyer didn't provide one. The Actor never guesses. With a value filter set, notices without a value are excluded.
- **Value range.** `valueLow`/`valueHigh` are the lowest and highest positive amounts stated anywhere on the notice (overall value, lots, awards, contracts). They are not an estimate.
- **Region filter** uses exact names (case-insensitive), so `East` does not match `North East`; use the full name (`East of England`) or a NUTS code. A NUTS code matches itself and the codes nested under it (`UKM` matches `UKM82`), and a UK region name also matches notices that only carry NUTS codes inside that region. Region data comes from delivery addresses as published by the buyer.
- **Monitor mode** remembers delivered notices per filter combination, in a named key-value store in your Apify account (`uk-tenders-ocds-monitor`), saved after every page. Changing the filters starts a new history. The history keeps the most recent 200,000 notice IDs per filter combination.
- **Volume caps.** By default each source reads at most 200 pages (20,000 releases) per run; raise `maxPagesPerSource` for very long windows.
- **No attachments.** Documents are returned as links; files are not downloaded.
- **Notices vs. procurements.** Each published notice (release) appears once per run. One procurement (`ocid`) can produce several notices over time.

### Legal and data note

- Both sources are official UK government services. Contracts Finder and Find a Tender (both run by the UK Cabinet Office) publish this content under the **Open Government Licence v3.0**, which permits copying, adaptation and commercial use with attribution. Every record includes a `license` field with the attribution: *Contains public sector information licensed under the Open Government Licence v3.0.*
- The Actor uses only the public, anonymous OCDS read APIs. It does not log in, scrape HTML or solve CAPTCHAs. Requests are throttled (1 second apart by default) and use a descriptive User-Agent.
- Personal contact data (names, e-mails, phones) is removed before saving.
- This Actor is not affiliated with the UK Government, Crown Commercial Service or Cabinet Office. Always check the official notice before you bid.

### FAQ

**Do I need an API key or an account on Contracts Finder or Find a Tender?**
No. Both OCDS read APIs are anonymous. You only need an Apify account.

**Why two sources?**
Contracts Finder covers lower-value contracts in England and central government. Find a Tender covers UK-wide higher-value contracts and, since the Procurement Act 2023 came into force (February 2025), carries most new tender, pipeline and award notices. Using both gives the widest coverage.

**How fresh is the data?**
Each run queries the official APIs live, so you get whatever is published at that moment.

**How do I get daily alerts?**
Set your filters, turn on `onlyNew`, and create a daily schedule. Add a webhook or integration (e.g. Slack, e-mail, Google Sheets) to receive the new notices.

**Can I see who won a contract?**
Yes. Select the `award` stage. Award notices fill `suppliers` (name, Companies House number where published, and scale) and `awardDate`. Use the *Awards & suppliers* view in the Output tab.

**Can I get the full OCDS JSON?**
Yes. Set `outputMode` to `raw`. Personal contact data is still removed, the source is added as `_source` and the licence attribution as `_license`.

**Will I get duplicates?**
Not within a run: each published notice appears once. A single procurement can have several notices over time (for example a tender notice and later an award notice); both are returned because they are different notices.

**Why do some notices have no value or region?**
The buyer didn't publish one. The fields are left empty rather than estimated.

**How much will a run cost?**
$0.0025 per notice saved. Set `maxItems` or Apify's "Max cost per run" to cap it; for example, `maxItems: 400` can never cost more than $1.00 in notice charges.

**Is this legal to use commercially?**
The data is published under the Open Government Licence v3.0, which allows commercial reuse with attribution. Each record carries the attribution in its `license` field.

# Changelog

This Actor's version history is a separate document: https://apify.com/civitab/uk-tenders-ocds/changelog.md

# Actor input Schema

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

CF = Contracts Finder (England, lower-value + all central gov). FTS = Find a Tender (UK-wide, higher-value / Procurement Act 2023 notices).

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

planning = pipeline / early engagement, tender = open opportunities, award = awarded contracts.

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

Look back this many days from now (ignored if 'Date from' is set).

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

Optional start (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS, UTC). Contracts Finder filters by published date, Find a Tender by last-updated date.

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

Optional end (default: now).

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

Keep notices whose title or description contains ANY of these words (case-insensitive).

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

Keep notices with a CPV code starting with any of these, e.g. 72 (IT services) or 45000000.

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

Keep notices whose highest stated value is at least this.

## `maxValue` (type: `integer`):

Keep notices whose lowest stated value is at most this.

## `region` (type: `string`):

Exact region name (e.g. London, Scotland, North East, East of England) or a NUTS code (e.g. UKM, UKI3; includes the codes under it). Exact match: "East" does not match "North East". Separate several with commas.

## `buyerName` (type: `string`):

e.g. NHS, Council, Ministry of Defence.

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

Stop after this many notices. You pay per notice, so this caps cost.

## `outputMode` (type: `string`):

flat = clean one-row-per-notice table. raw = full OCDS release (personal contact data removed).

## `onlyNew` (type: `boolean`):

Skip notices this Actor already returned to you in earlier runs with the same filters. Schedule it daily for alerts.

## `maxPagesPerSource` (type: `integer`):

Safety cap (100 notices per page).

## `requestDelaySecs` (type: `number`):

Politeness delay between API calls.

## Actor input object example

```json
{
  "sources": [
    "CF",
    "FTS"
  ],
  "stages": [
    "tender"
  ],
  "daysBack": 3,
  "dateFrom": "",
  "dateTo": "",
  "keywords": [],
  "cpvCodes": [],
  "minValue": null,
  "maxValue": null,
  "region": "",
  "buyerName": "",
  "maxItems": 50,
  "outputMode": "flat",
  "onlyNew": false,
  "maxPagesPerSource": 200,
  "requestDelaySecs": 1
}
```

# Actor output Schema

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

Dataset items. Field types are in the dataset schema.

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

JSON: items pushed, request success rate, stop reason, charged events.

# 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 = {
    "sources": [
        "CF",
        "FTS"
    ],
    "stages": [
        "tender"
    ],
    "daysBack": 3,
    "keywords": [],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("civitab/uk-tenders-ocds").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 = {
    "sources": [
        "CF",
        "FTS",
    ],
    "stages": ["tender"],
    "daysBack": 3,
    "keywords": [],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("civitab/uk-tenders-ocds").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 '{
  "sources": [
    "CF",
    "FTS"
  ],
  "stages": [
    "tender"
  ],
  "daysBack": 3,
  "keywords": [],
  "maxItems": 50
}' |
apify call civitab/uk-tenders-ocds --silent --output-dataset

```

## MCP server setup

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

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/OdlrBUo94cyknJdVL/builds/SyLmE6HUivAT9VzOQ/openapi.json
