# EU & UK Tenders Scraper: TED + Find a Tender (`treesap_1127/eu-uk-tenders-scraper`) Actor

Open public tender notices from EU TED and UK Find a Tender, filtered by keyword, CPV code and country, in one format.

- **URL**: https://apify.com/treesap_1127/eu-uk-tenders-scraper.md
- **Developed by:** [TaeSung Lee](https://apify.com/treesap_1127) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 apify-default-dataset-items

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

## EU & UK Tenders Scraper: TED + Find a Tender

Collect open public procurement notices from two official sources, the EU's **TED** (Tenders Electronic Daily) and the UK's **Find a Tender**, in one flat format. Filter by keyword, CPV code and buyer country, and get title, buyer, estimated value, currency, submission deadline and a link for every notice. No login, no API key, no browser: the Actor calls the official public JSON APIs.

Typical uses: a daily alert for new tenders in your field, a lead list for bid teams and agencies, market research on public spending, or feeding a spreadsheet or CRM.

### What you get

- Competition notices only (calls for tenders), published within the last 1 to 30 days.
- One record per notice with the same 17 fields from both sources.
- Open notices only by default (notices whose deadline has passed are dropped).
- No personal data: contact persons' names, e-mails and phone numbers are never read or saved.

### Input

All fields are optional. The defaults return the newest 100 open notices from both sources.

| Field | Type | Description |
|---|---|---|
| `keywords` | list of text | Notices matching any keyword are kept. Empty means no keyword filter. |
| `cpvCodes` | list of text | CPV codes, 2 to 8 digits. Matching is hierarchical: `45000000` matches every construction code starting with `45`, `72220000` matches `7222...`. |
| `countries` | list of text | Buyer country as ISO alpha-3 code (`DEU`, `FRA`, `GBR`). Empty means all. Find a Tender is only queried when `GBR` is included or the list is empty. |
| `sources` | list | `ted`, `uk` or both. |
| `publishedWithinDays` | integer 1-30 | Notices published since 00:00 UTC of (today minus N days). Default 3. |
| `onlyOpen` | boolean | Drop notices whose submission deadline has passed. Default true. Notices without a deadline are kept. |
| `maxItems` | integer 1-5000 | Maximum notices saved, both sources together. Default 100. |

Example:

```json
{
  "keywords": ["software"],
  "cpvCodes": ["72000000"],
  "countries": ["DEU", "GBR"],
  "publishedWithinDays": 3,
  "maxItems": 100
}
```

#### How keywords work

TED uses its own full-text search over the whole notice in all languages, so a match may come from a field not shown in the output. Find a Tender has no search parameter, so the Actor matches keywords case-insensitively against the title and description. Matching is by substring, so `soft` also matches `software`.

#### Publication window and daily runs

`publishedWithinDays` counts whole UTC days. Running daily with `1` can return the same notice twice across two runs, which is intentional so that nothing is missed. De-duplicate by `noticeId` in your own workflow. The Actor keeps no state between runs.

### Output

Each dataset item has these fields:

| Field | Description |
|---|---|
| `source` | `TED` or `UK Find a Tender` |
| `noticeId` | Notice number in the source portal |
| `title`, `description` | Notice title and procurement description, original text (TED: English when available) |
| `buyerName`, `buyerCountry` | Contracting authority and ISO alpha-3 country |
| `placeOfPerformance` | NUTS or region codes |
| `contractType` | `supplies`, `services` or `works` |
| `procedureType` | Procedure as published, for example `open` |
| `cpvCodes` | CPV classification codes |
| `estimatedValue`, `currency` | Estimated value as published, `null` when not published. TED: total value, otherwise the sum of lot values in one currency |
| `publicationDate` | TED: `YYYY-MM-DD`; UK: ISO timestamp |
| `submissionDeadline` | Latest submission deadline, `null` when not published |
| `url` | Link to the notice page |
| `attribution`, `licenseUrl` | Attribution statement and licence of the source |

See `sample-output.json` for real examples. Results are sorted by publication date, newest first, and the dataset has an Overview table view.

### How it works

- TED: one search request per page of up to 250 notices, sorted newest first. The Actor stops as soon as `maxItems` notices are collected.
- Find a Tender: the release feed is read page by page (100 releases per page, newest first). Only tender releases are kept, and only the latest release per procedure, so a cancelled tender does not appear. The feed cannot be searched, so a selective keyword over many days means many pages; a 7-day keyword run read 25 pages and took about 5 to 6 minutes locally. On the Apify platform the UK feed answered more slowly (up to about 14 seconds per page in our test), so a keyword scan over many days can take many minutes. The Console prefill queries TED only for that reason. Narrow `publishedWithinDays` or leave the keyword empty for faster runs.
- Politeness: requests to each official host are sequential with at least 1 second between them. Failed requests are retried 3 times with growing waits (honouring `Retry-After`). Blocked or changed responses are not bypassed.
- If one source fails, results from the other are saved and the run succeeds with a status message naming the failed source. The run fails only when every requested source fails.
- No results is not an error: the run succeeds with a warning that no notices matched.

### Pricing

The Actor uses pay per event: you are charged per notice saved to the dataset. The draft price is **$0.005 per notice** (about $5 per 1,000). The final price is whatever is configured in the Apify Console when the Actor is published. The Actor stops by itself when your maximum cost per run is reached.

### Legal and FAQ

#### Is the data allowed to be reused?

TED: the legal notice states that, unless otherwise noted, procurement notices published in the Supplement to the Official Journal of the European Union can be freely reused for commercial or non-commercial purposes. Each TED record carries `© European Union, https://ted.europa.eu (reformatted)`; the data is reformatted by this Actor. The TED logo is not used.

Find a Tender: notice data is published under the Open Government Licence v3.0. Each UK record carries "Contains public sector information licensed under the Open Government Licence v3.0." and the licence link. The licence excludes personal data, which this Actor does not collect.

#### Is this an official service?

No. This Actor is not affiliated with, endorsed by or operated by the European Union, the UK Government or the Cabinet Office. It uses their public APIs at a low request rate.

#### Does it collect personal data?

No. Contact person fields are not requested or read.

#### Responsibility

You are responsible for complying with the terms, licences and laws that apply to your use of the data. This section summarises the source terms as checked on 2026-11-05 and is not legal advice.

#### Why did I get fewer notices than `maxItems`?

Fewer notices matched your filters in the chosen period. The status message tells you how many were saved and whether a source failed.

#### Can I get award results or other portals?

Not in this version: only competition notices from TED and Find a Tender.

# Actor input Schema

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

Notices matching any keyword are kept. TED uses its full-text search (whole notice, all languages); Find a Tender matches the title and description (case-insensitive). Leave empty for no keyword filter.

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

CPV codes (2-8 digits) matched by prefix after trailing zeros are dropped: 45000000 matches all construction (45...), 72000000 matches IT services (72...). Leave empty for all.

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

ISO 3166-1 alpha-3 country codes of the buyer, for example DEU, FRA, GBR. Empty means all. Find a Tender is only requested when GBR is included (or the list is empty); TED is requested for any non-GBR country.

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

Which official APIs to query: TED (EU) and/or Find a Tender (UK).

## `publishedWithinDays` (type: `integer`):

Only notices published since 00:00 UTC of (today minus N days). 3 keeps Friday notices visible on a weekend run. With daily runs of 1, one day may overlap; deduplicate by noticeId.

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

Drop notices whose submission deadline has passed. Notices without a deadline are kept.

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

Maximum number of notices to save (both sources together).

## Actor input object example

```json
{
  "keywords": [
    "software"
  ],
  "cpvCodes": [],
  "countries": [],
  "sources": [
    "ted"
  ],
  "publishedWithinDays": 3,
  "onlyOpen": true,
  "maxItems": 20
}
```

# 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 = {
    "keywords": [
        "software"
    ],
    "sources": [
        "ted"
    ],
    "publishedWithinDays": 3,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("treesap_1127/eu-uk-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 = {
    "keywords": ["software"],
    "sources": ["ted"],
    "publishedWithinDays": 3,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("treesap_1127/eu-uk-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 '{
  "keywords": [
    "software"
  ],
  "sources": [
    "ted"
  ],
  "publishedWithinDays": 3,
  "maxItems": 20
}' |
apify call treesap_1127/eu-uk-tenders-scraper --silent --output-dataset

```

## MCP server setup

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