# EU Public Tenders Search (`thesjostrandproject/eu-tenders-search`) Actor

Search EU public tender notices by keyword, buyer country, CPV code and publication date through the EU's public procurement-notice API. Clean, typed records with no contact persons and no award winners. No login, no scraping. Pay per record.

- **URL**: https://apify.com/thesjostrandproject/eu-tenders-search.md
- **Developed by:** [The Sjöstrand Project](https://apify.com/thesjostrandproject) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 tender 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

## EU Public Tenders Search

### What this Actor returns

Search the public procurement notices that buyers across the EU publish, by keyword, buyer country, CPV code and publication date, and get one clean, typed record per notice. Every record has the same 13 fields, and a value the notice does not have is `null`, never a missing key.

- `publication_number`, `notice_type`, `publication_date`: which notice it is, what kind, and when it was published
- `deadline_date`: the earliest deadline for tenders, when the notice has one
- `buyer_org`, `buyer_country`, `performance_nuts_code`: who is buying, from which country, and where the work is done
- `cpv_codes`, `notice_title`, `estimated_value`, `currency`, `procedure_type`: what is being bought, its title, its estimated value and the procedure
- `notice_url`: the link to the full notice on the source's website

The Actor reads the EU's public procurement-notice search API and nothing else. It does not scrape any website, and there is no login.

### Who it helps

- **Businesses that bid for public contracts**, who want a daily or weekly list of new tenders in their sector and countries, ready for a spreadsheet or a CRM.
- **Analysts and developers** who want tidy tender data for market research or their own alerts, through the Apify API, without writing their own client for the source.

### Quick start

1. Open the Input tab and leave the defaults as they are. They search the notices published on one past day, 1 October 2026, so a first run returns up to 10 records (the default Maximum records).
2. Press Start. The default run costs about $0.03: 10 records at $0.003 each, plus Apify's tiny start event (see What it costs). To try it on even less, set Maximum records to 1.
3. Open the Output tab, check the records, then set your own keywords, countries, CPV codes and dates.

### Input

| Field | What it does | Default |
|---|---|---|
| Keywords | Words to look for anywhere in the notice, in any language. Letters, digits and spaces only. | empty |
| Published from | Only notices published on or after this date. | `2026-10-01` |
| Published to | Only notices published on or before this date. | `2026-10-01` |
| Buyer countries | Three-letter country codes like `DEU`, `FRA` or `SWE`, separated by commas. | empty |
| CPV codes | 8-digit CPV codes like `45000000`, separated by commas. A broad code includes the codes under it. | empty |
| Active notices only | Only notices that are still active, such as tenders still open for bids. | off |
| Language | Three-letter code for the title and buyer name, like `eng`, `deu` or `fra`. | `eng` |
| Maximum records | How many records to return at most, from 1 to 1000. | 10 |

Give at least one of a date, keywords, a country or a CPV code. Results come newest publication first.

### Output

One record per notice. The record below is one the default input returned on 12 October 2026, unchanged except for the link.

```json
{
  "publication_number": "677854-2026",
  "notice_type": "cn-standard",
  "publication_date": "2026-10-01",
  "deadline_date": "2026-11-02",
  "buyer_org": "Landkreis Schaumburg - Vergabestelle",
  "buyer_country": "DEU",
  "performance_nuts_code": "DE928",
  "cpv_codes": ["45431100", "45431200"],
  "notice_title": "Germany – Floor-tiling work – Sanierung Grundschule Evesen - Fliesen- und Plattenarbeiten",
  "estimated_value": null,
  "currency": null,
  "procedure_type": "open",
  "notice_url": "(the notice's page on the source's website)"
}
```

In a real record, `notice_url` is the full link to the notice.

### What it costs

You pay for one event, **Tender notice**, at $0.003 per record returned. Apify also adds its standard run-start event, $0.00005 per start for an Actor run with up to 1 GB of memory; it is the platform's own event, not ours. The default run of 10 records costs about $0.03. Records the Actor leaves out (see below) are never charged.

For example, a search that returns 200 notices costs $0.60. If your Apify plan includes monthly credit, a small run draws on that credit.

If the source cannot be reached or keeps answering with errors, the run stops with a clear status message, and only the records already returned are charged.

### Limits and honest notes

- **No contact persons, emails or phone numbers, and no award winners.** The Actor never asks the source for those fields, and it leaves out any notice whose title or other output text happens to contain an email address or a phone number.
- **Titles are written by the source.** A notice title is free text from the buyer's notice, as published.
- **One estimate per notice.** `estimated_value` is the value of the whole procurement. When a notice gives only separate values per lot, it is `null`.
- **Up to 1000 records per run.** For more, split the search by date range or country.
- **Pace.** The source's fair-use limit is 700 requests per minute. The Actor sends at most one request per second and waits as long as the source asks when it is busy.
- **Freshness** follows the source, which publishes new notices on working days. The Actor reads each notice as the source gives it at the time of your run.

### Questions

**Why did my run return fewer records than I asked for?**
Either fewer notices matched, or some were left out because they had no notice page or lacked a title, a buyer name or a publication date, or held contact details in their text. You were not charged for those.

**What does the run say when something is wrong?**
It says what it found and what to try. "Give at least one of: a publication date, keywords, a buyer country or a CPV code" means the input was empty: add one. A message about country codes, CPV codes or dates names the field to fix and shows the right shape. "Could not be reached" or "kept answering" means the source is busy; wait a few minutes and run again.

**How do I find only tenders I can still bid for?**
Turn on "Active notices only", and look at `deadline_date`.

**Which country codes and CPV codes do I use?**
Countries are three-letter codes such as `DEU` for Germany or `ESP` for Spain. CPV codes are the EU's 8-digit codes for what is bought; a broad code such as `45000000` (construction work) includes the codes under it.

**Can I run it on a schedule?**
Yes, with the Apify scheduler or the API. Set the dates to the period you want; each run is independent and charged per record returned.

### Data source and AI note

Data source: the Search API of TED (Tenders Electronic Daily), the online version of the Supplement to the Official Journal of the European Union, published by the Publications Office of the European Union; the notices are reused under their licence, the Commission's reuse policy (Commission Decision 2011/833/EU), which allows commercial reuse. `publication_date` is the date the notice was published on TED, taken from the API's `publication-date` field; it is not a last-updated date, and the records have no source_updated_at field.

This tool is not affiliated with, endorsed by or sponsored by the Publications Office of the European Union or the European Commission.

This tool was built with the help of AI coding assistants.

### Help

Questions, a record that looks wrong, or an idea for a field: please open an issue on the Issues tab. Thank you for the report; every one is read.

# Actor input Schema

## `keywords` (type: `string`):

Words to look for anywhere in the notice, in any language, like road maintenance. Letters, digits and spaces only. Leave empty for any.

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

Only notices published on or after this date (YYYY-MM-DD). Leave empty for no lower bound.

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

Only notices published on or before this date (YYYY-MM-DD). Leave empty for no upper bound.

## `buyerCountries` (type: `string`):

Only buyers in these countries, as three-letter codes like DEU, FRA or SWE, separated by commas. Leave empty for any.

## `cpvCodes` (type: `string`):

Only notices with these 8-digit CPV codes (what is being bought), like 45000000 for construction work. A broad code includes the codes under it. Separate several with commas.

## `activeOnly` (type: `boolean`):

Only notices that are still active, such as tenders still open for bids.

## `language` (type: `string`):

Three-letter code of the language for the title and buyer name, like eng, deu or fra. When a notice lacks it, English is used, then the first language given.

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

How many notices to return at most. You pay only for records returned.

## Actor input object example

```json
{
  "keywords": "",
  "publishedFrom": "2026-10-01",
  "publishedTo": "2026-10-01",
  "buyerCountries": "",
  "cpvCodes": "",
  "activeOnly": false,
  "language": "eng",
  "maxItems": 10
}
```

# Actor output Schema

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

The tender notices of this run, one per notice, with the 13 fields described in the dataset schema (number, type, dates, buyer, place, CPV codes, title, estimated value, link).

# 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": "",
    "publishedFrom": "2026-10-01",
    "publishedTo": "2026-10-01",
    "buyerCountries": "",
    "cpvCodes": "",
    "activeOnly": false,
    "language": "eng",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("thesjostrandproject/eu-tenders-search").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": "",
    "publishedFrom": "2026-10-01",
    "publishedTo": "2026-10-01",
    "buyerCountries": "",
    "cpvCodes": "",
    "activeOnly": False,
    "language": "eng",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("thesjostrandproject/eu-tenders-search").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": "",
  "publishedFrom": "2026-10-01",
  "publishedTo": "2026-10-01",
  "buyerCountries": "",
  "cpvCodes": "",
  "activeOnly": false,
  "language": "eng",
  "maxItems": 10
}' |
apify call thesjostrandproject/eu-tenders-search --silent --output-dataset

```

## MCP server setup

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

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/wGTXQaObIeUdWoZJk/builds/a9OlHfCk0Y0KobSu4/openapi.json
