# UK EU Tender Notices Scraper (`axiomworks/uk-eu-public-tenders-scraper`) Actor

Search Contracts Finder and TED for public tender notices and get titles, buyers, countries, status, publication and deadline dates, values and CPV codes. The Actor combines results from both official APIs in a shared record format, with no API key required.

- **URL**: https://apify.com/axiomworks/uk-eu-public-tenders-scraper.md
- **Developed by:** [Axiom Works](https://apify.com/axiomworks) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.76 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 & EU Public Tenders Scraper

**Returns:** one row per tender notice with title, buyerName, country, status, publishedDate, deadlineDate, valueHigh and cpvCodes.
**Input:** `{"searchTerms":["construction"],"sources":["contractsFinder","ted"],"status":"open","maxItems":20}`
**Best for:** public tender monitoring and bid alerts, sales lead lists for government suppliers, market research on public procurement spending.

This public tenders scraper works as a UK Contracts Finder API and EU TED API in one tool. It searches both official procurement portals, then returns every notice in the same flat format, so you can compare UK and European opportunities in one table.

```json
{
  "source": "contractsFinder",
  "id": "cf-83d37637-0fb5-4370-9b4e-53882b62a218",
  "title": "BBV HS2 MWCC NORTH - Fire Engineer Services",
  "buyerName": "Balfour Beatty Vinci HS2",
  "country": "GBR",
  "publishedDate": "2026-10-08T08:10:07+01:00",
  "deadlineDate": "2026-10-16T12:00:00+01:00",
  "valueHigh": 2500000
}
```

### What does UK & EU Public Tenders Scraper do?

UK & EU Public Tenders Scraper searches two open government data sources: Contracts Finder, the UK portal for public sector contracts, and Tenders Electronic Daily (TED), the EU journal for public procurement. You give it keywords, CPV classification codes, a status, a date range, a value range and optionally buyer countries or UK regions. It returns the matching tender notices as one dataset.

Both portals publish their data through official, anonymous APIs, so the Actor does not open web pages, does not need a login and does not need a proxy. Requests are made at about one per second per host, with automatic retries when a portal answers with a rate limit or a temporary error.

Typical uses include building a daily list of new open tenders in your sector, collecting awarded contracts to see which suppliers win work, finding buyers that regularly procure a given service, and feeding procurement data into a spreadsheet, CRM or dashboard.

You can also paste individual notice links. The Actor fetches those notices directly, which is handy when you want the full record for a tender you found elsewhere.

### What data can you get?

Every record has the same fields, whichever portal it came from. Fields that a portal does not publish are returned as null (or an empty list), so your downstream code never has to handle two shapes.

| Field | Type | Description |
|---|---|---|
| `id` | string | Stable unique ID: `cf-<GUID>` for Contracts Finder, `ted-<publication number>` for TED |
| `sourceUrl` | string | Notice page the record came from |
| `source` | string | `contractsFinder` or `ted` |
| `noticeId` | string | Contracts Finder GUID or TED publication number |
| `title` | string | Tender title |
| `description` | string | Full description text (TED descriptions are in the buyer's language) |
| `buyerName` | string | Contracting authority |
| `buyerCity` | string | Buyer city (TED) |
| `buyerEmail` | string | Buyer organisation contact email (TED) |
| `buyerWebsite` | string | Buyer organisation website (TED notices that publish it; null for Contracts Finder) |
| `buyerLegalType` | string | Buyer legal type code(s) such as `body-pl` (TED; null for Contracts Finder) |
| `country` | string | ISO alpha-3 buyer country, `GBR` for Contracts Finder |
| `region` | string | UK region text, or TED NUTS place-of-performance codes |
| `postcode` | string | Buyer postcode (Contracts Finder or TED); contact postcode fallback in Contracts Finder notice-URL mode |
| `noticeType` | string | Notice type such as `Contract` or `cn-standard` |
| `status` | string | Open, Closed, Awarded or Withdrawn |
| `procedureType` | string | Procurement procedure (TED) |
| `contractNature` | string | services, supplies or works (TED) |
| `publishedDate` | string | Publication date |
| `deadlineDate` | string | Tender submission deadline; earliest valid lot date on TED |
| `valueLow` / `valueHigh` | number | Disclosed value range; null when the buyer did not disclose a value |
| `currency` | string | Currency of the values |
| `cpvCodes` | array | Unique 8-digit CPV codes |
| `cpvDescription` | string | Plain-text CPV description (Contracts Finder) |
| `awardedDate` | string | Award date (Contracts Finder award notices) |
| `awardedValue` | number | Contracts Finder awarded value; sum of disclosed TED winning tender values |
| `awardedSuppliers` | array | Names of winning suppliers |
| `suitableForSme` | boolean | SME suitability flag (Contracts Finder) |
| `contractStart` / `contractEnd` | string | Planned contract dates (Contracts Finder; TED when lots publish dates) |
| `documentUrls` | object | TED notice PDF and XML links |
| `searchTerm` | string | The input term that found the record |
| `scrapedAt` | string | ISO timestamp of the scrape |

Many tenders do not disclose a value. In both portals a value of zero means not disclosed, and the Actor turns it into null instead of reporting a misleading zero.

### How to use

1. Open the Actor in Apify Console and go to the Input tab.
2. Type one or more search terms, for example `software` or `cleaning`. You can also enter CPV codes instead of keywords.
3. Choose the sources. Both are selected by default.
4. Pick a status. `open` returns tenders that still accept bids, `awarded` returns notices that name a winner, `closed` returns tenders whose deadline has passed and `any` removes the filter.
5. Set `maxItems` to cap the number of records and click Start.
6. Download the dataset as JSON, CSV, Excel or HTML, or read it through the API.

When you select several sources and several search terms, the Actor splits `maxItems` evenly across every source and term combination, then tops up from the combinations that still have results. The run never returns more than `maxItems` records. Duplicate notices are removed, so the same tender found by two terms appears once.

Contracts Finder does not return results sorted by date, and it does not page. The Actor handles this for you: when a search matches more than a thousand notices it splits the date range into smaller windows and walks backwards from the newest one, so the newest notices come first.

### Input

For recurring tender alerts, set `datePreset` to `last_7_days`, `last_30_days`, `last_90_days` or `year_to_date`. The default `none` leaves the existing date filters unchanged. Day presets include today and use UTC calendar dates; a seven-day run includes today plus the previous six days. An explicit `publishedFrom` takes precedence over the preset, and `publishedTo` remains an independent upper bound. For daily monitoring, use an overlapping window and deduplicate across runs using `id`, since publications can arrive late.

To refresh known notices without rebuilding links, pass `noticeIds`, for example `{"noticeIds":["681373-2026"],"maxItems":1}`. Contracts Finder GUIDs also work. You can combine IDs with existing `noticeUrls`; duplicate IDs are emitted once. With neither keywords nor CPV codes, these inputs run direct lookups only. Adding keywords or CPV codes also starts searches, sharing the same total `maxItems` cap. A nonexistent ID is skipped, and other targets continue. Direct lookups ignore search filters and `sources`: the link or identifier chooses the portal. If every direct target is missing or unrecognised, the run returns zero records and its status message explains which link or identifier format to supply.

| Field | Type | Description |
|---|---|---|
| `searchTerms` | array | Keywords to search, for example `["software"]`. Each term is searched in every selected source |
| `sources` | array | `contractsFinder`, `ted` or both (default both) |
| `status` | string | `open` (default), `awarded`, `closed` or `any` |
| `maxItems` | integer | Total record cap, 1 to 10000, default 50 |
| `publishedFrom` / `publishedTo` | string | Publication date bounds in YYYY-MM-DD format |
| `cpvCodes` | array | 8-digit CPV codes, for example `["72000000"]` |
| `countries` | array | Buyer countries as alpha-3 codes, TED only |
| `ukRegions` | array | UK regions to keep, Contracts Finder only |
| `minValue` / `maxValue` | integer | Contract value bounds in the notice currency |
| `datePreset` | string | Relative publication window; default `none`. Explicit publishedFrom overrides it. |
| `noticeIds` | array | Contracts Finder GUIDs or TED publication numbers; default empty. |
| `noticeUrls` | array | Contracts Finder notice URLs or TED notice URLs, as `{"url": ...}` objects |
| `includeDescription` | boolean | Include the full description text (default true) |
| `proxyConfiguration` | object | Apify Proxy settings; off by default because both APIs work without it |

Example input for open IT tenders from German and French buyers:

```json
{
  "cpvCodes": ["72000000"],
  "sources": ["ted"],
  "countries": ["DEU", "FRA"],
  "status": "open",
  "maxItems": 20
}
```

Example input for awarded UK cleaning contracts published since September, worth at least 10,000:

```json
{
  "searchTerms": ["cleaning"],
  "sources": ["contractsFinder"],
  "status": "awarded",
  "publishedFrom": "2026-09-01",
  "minValue": 10000,
  "maxItems": 15
}
```

The UK region filter is applied by the Actor after download, because the Contracts Finder API ignores it. Notices marked "Any region" are kept. The country filter exists only on the TED side, so it has no effect on UK notices.

### Output

Each run writes one dataset item per notice. A Contracts Finder item looks like this:

```json
{
  "source": "contractsFinder",
  "id": "cf-12f64315-e80b-4070-8d3f-08816bd8e838",
  "sourceUrl": "https://www.contractsfinder.service.gov.uk/Notice/12f64315-e80b-4070-8d3f-08816bd8e838",
  "title": "Repair Works to Heritage Site - Ellesmere Yard",
  "buyerName": "Canal & River Trust",
  "country": "GBR",
  "postcode": "SY12 9AA",
  "noticeType": "Contract",
  "status": "Open",
  "publishedDate": "2026-10-01T15:49:55+01:00",
  "deadlineDate": "2026-10-16T17:00:00+01:00",
  "valueLow": 3000000,
  "valueHigh": 4000000,
  "currency": "GBP",
  "cpvCodes": ["45212350"],
  "cpvDescription": "Buildings of particular historical or architectural interest",
  "suitableForSme": true,
  "searchTerm": "construction"
}
```

Titles and descriptions from Contracts Finder arrive with HTML entities such as `&amp;`; the Actor decodes them into plain text. TED notices carry titles that the EU machine-translates into every official language, so the English title is used when present. Buyer names and descriptions on TED come in the original language of the notice.

### How much does it cost?

The Actor is charged per result: you pay for each tender record added to the dataset, and nothing for searches that return no records. See the Pricing tab of the Actor for the current rate. Because `maxItems` is a hard cap, you can set the most a run can return before you start it.

### Use with AI agents (MCP)

You can call this Actor from any MCP-compatible assistant through the Apify MCP server. Add the Actor to the tools list in the server URL:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=axiomworks/uk-eu-public-tenders-scraper"
    }
  }
}
```

Example prompts:

1. "Find open UK and EU tenders about cybersecurity and list the buyer, deadline and value for the ten newest."
2. "Search awarded Contracts Finder notices for cleaning services since 1 September and tell me which suppliers won."
3. "Get the notice at https://ted.europa.eu/en/notice/-/detail/681373-2026 and summarise what the buyer wants."

### Integrations and API

Run the Actor from code with the Apify API clients, or connect it to Make, Zapier, n8n, Google Sheets or a webhook on the Integrations tab. Schedule it daily with a `publishedFrom` date to build an alert feed.

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("axiomworks/uk-eu-public-tenders-scraper").call(run_input={
    "searchTerms": ["software"],
    "status": "open",
    "maxItems": 50,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["buyerName"], item["deadlineDate"])
```

**JavaScript**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('axiomworks/uk-eu-public-tenders-scraper').call({
    searchTerms: ['software'],
    status: 'open',
    maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
process.stdout.write(`${items.length} tenders\n`);
```

**cURL**

```bash
curl -X POST "https://api.apify.com/v2/acts/axiomworks~uk-eu-public-tenders-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms": ["software"], "status": "open", "maxItems": 50}'
```

### FAQ

**Why do I get fewer results than I asked for?**
`maxItems` is a ceiling, not a target. A narrow keyword on the UK portal can match only a handful of open notices, especially since most above-threshold UK tenders moved to the separate Find a Tender service under the Procurement Act 2023. Add TED as a source, broaden the keyword, or switch the status to `any`.

**Why are some value fields empty?**
Buyers often do not publish an estimated value. The portals then report zero, and this Actor returns null.

**What currency are the values in?**
Contracts Finder values are in GBP. TED values use the currency stated in the notice, usually EUR, and the `currency` field says which.

**Can I fetch one specific notice?**
Yes. Put its link in `noticeUrls` as a URL object, or its TED publication number or Contracts Finder GUID in `noticeIds`. In that mode a few extra Contracts Finder fields, such as the buyer contact email and town, are filled from the notice itself.

**What happens with a bad input?**
A search that matches nothing finishes with zero items and a warning. A notice link that does not exist is skipped with a warning. If every request fails, the run stops with an error message that says so.

**Does it need a proxy or an API key?**
No. Both APIs are open and anonymous. The proxy setting exists in case your own network blocks them.

### Is it legal to scrape UK & EU Public Tenders?

The data comes from official open data services. Contracts Finder notices are published under the Open Government Licence, and TED data may be reused under the European Commission's reuse policy. The Actor reads only public notices through the portals' public APIs, with no login and a modest request rate. Notices can contain names or contact details of procurement officers, so handle them under the data protection rules that apply to you, such as GDPR. This Actor is not affiliated with or endorsed by Contracts Finder, TED or the European Union. This is general information, not legal advice.

### Feedback

Found a missing field, a wrong value or a filter you need? Open an issue from the Actor page in the Apify Store and include the input you used and the notice link. Reports with a concrete example are fixed fastest.

# Actor input Schema

## `searchTerms` (type: `array`):

Tender keywords, e.g. \["construction", "cleaning"]; a single string also works through the API. Each term searches every selected source. Omit for CPV-only or direct-notice lookups; with no keywords, CPV codes or direct targets, each source is searched without keywords. No matches return 0 records; broaden filters or use status "any".

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

Portals to search: "contractsFinder" (UK Contracts Finder) and/or "ted" (EU Tenders Electronic Daily), e.g. \["ted"]. Default is both. maxItems is shared across both. Applies to searches; direct noticeUrls and noticeIds determine their own portal.

## `status` (type: `string`):

One of "open" (accepting bids now), "awarded" (contract awarded), "closed" (deadline passed) or "any" (no filter). Default "open". If a search returns 0 items, try "any". Direct notice lookups ignore this search filter.

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

Total number of tender records to return across all sources and search terms combined, 1-10000, default 50. Use a small value (e.g. 5) for a quick test.

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

Only notices published on or after this date, format YYYY-MM-DD, e.g. '2026-09-01'. Leave empty for no lower bound.

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

Only notices published on or before this date, format YYYY-MM-DD, e.g. '2026-10-02'. Leave empty for no upper bound.

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

8-digit Common Procurement Vocabulary codes, e.g. \["72000000"] for IT services. Contracts Finder also matches child codes. A "-check digit" suffix such as "-8" is ignored.

## `countries` (type: `array`):

ISO 3166-1 alpha-3 country codes (three letters, not two) of the buying authority, e.g. \["DEU", "FRA", "IRL"]. Applies to TED only; empty = all countries.

## `ukRegions` (type: `array`):

Contracts Finder regions, e.g. \["Wales", "London"], chosen from the listed region names. Notices marked "Any region" are also kept. Empty means all regions. Has no effect on TED or direct notice lookups.

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

Only notices with a contract value of at least this number, in the notice currency (GBP for UK, usually EUR for TED), e.g. 10000.

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

Only notices with a contract value of at most this number, in the notice currency, e.g. 5000000.

## `noticeUrls` (type: `array`):

Direct notices as URL objects, e.g. \[{"url":"https://ted.europa.eu/en/notice/-/detail/681373-2026"}], or Contracts Finder /Notice/<GUID> links. API calls also accept URL strings or bare TED publication numbers. Search filters do not apply. Unknown/missing notices are skipped; with no keywords or CPV codes, only direct targets are fetched.

## `includeDescription` (type: `boolean`):

Whether to return full description text, e.g. true (default). Set false to return description: null and reduce output size; other fields are unchanged.

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

Proxy configuration object, e.g. {"useApifyProxy":false} (default: direct requests). Both official APIs support anonymous access. Set {"useApifyProxy":true} to retry through Apify Proxy if requests are blocked; exhausted failures skip the target and other targets continue.

## `datePreset` (type: `string`):

Publication window enum: none (default), last_7_days, last_30_days, last_90_days or year_to_date, e.g. "last_7_days" for recurring searches. Uses UTC calendar days including today. Explicit publishedFrom overrides it; publishedTo still applies. Direct lookups ignore date filters.

## `noticeIds` (type: `array`):

Fetch known notices by Contracts Finder GUID or TED publication number, e.g. \["681373-2026"]. IDs and noticeUrls share maxItems with searches. With no searchTerms or cpvCodes, only direct notices are fetched. Unknown IDs are skipped without failing the run.

## Actor input object example

```json
{
  "searchTerms": [
    "construction"
  ],
  "sources": [
    "contractsFinder",
    "ted"
  ],
  "status": "open",
  "maxItems": 20,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "datePreset": "none",
  "noticeIds": []
}
```

# Actor output Schema

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

One record per tender notice.

# 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 = {
    "searchTerms": [
        "construction"
    ],
    "sources": [
        "contractsFinder",
        "ted"
    ],
    "status": "open",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/uk-eu-public-tenders-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 = {
    "searchTerms": ["construction"],
    "sources": [
        "contractsFinder",
        "ted",
    ],
    "status": "open",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("axiomworks/uk-eu-public-tenders-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 '{
  "searchTerms": [
    "construction"
  ],
  "sources": [
    "contractsFinder",
    "ted"
  ],
  "status": "open",
  "maxItems": 20
}' |
apify call axiomworks/uk-eu-public-tenders-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,axiomworks/uk-eu-public-tenders-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/yY4TavoC3wkLF5i8U/builds/Bhb0x9480K9qffbbD/openapi.json
