# EU Public Tenders (TED) Scraper (`grit-77/eu-public-tenders-ted-scraper`) Actor

EU public tenders scraper from the TED Search API. Filter procurement notices by keyword, buyer country, CPV, publication date and value; export available notice fields as JSON.

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

## Pricing

$2.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 (TED) Scraper

EU tenders scraper returns public TED procurement notices with buyer, value, deadline, and source links. TED tenders API search results include notice type and publication date for filtering competitions and awards.

Search by keyword, buyer country, CPV code, publication date and notice value. The actor uses the public [TED v3 Search API](https://docs.ted.europa.eu/api/latest/search.html), without a TED API key. Results include competition notices, awards and other published notice types unless you filter them.

### What it returns

| Field | Type | Example value |
|---|---|---|
| `noticeId` | string | `"682380-2026"` |
| `title` | string | `"France – IT services: consulting, software development, Internet and support – Assistance technique sécurité"` |
| `titleLanguage` | string | `"eng"` |
| `noticeType` | string | `"can-standard"` |
| `publicationDate` | string | `"2026-10-05"` |
| `buyerName` | string | `"Mairie d'Asnières sur Seine"` |
| `buyerNames` | array | `["Mairie d'Asnières sur Seine"]` |
| `buyerCountries` | array | `["FRA"]` |
| `buyerCities` | array | `["Asnières sur seine"]` |
| `buyerWebsites` | array | `[]` |
| `cpvCodes` | array | `["72000000", "72413000", "72267100", "72600000", "72415000", "72317000"]` |
| `value` | number | `140444.52` |
| `currency` | string | `"EUR"` |
| `valueCurrencies` | array | `["EUR"]` |
| `valueSource` | string | `"total-value"` |
| `totalValue` | number | `140444.52` |
| `estimatedValue` | number | `0.0` |
| `estimatedCurrency` | string | `"EUR"` |
| `deadline` | string | `"2026-10-30T22:59:00Z"` |
| `deadlines` | array | `[]` |
| `deadlineSource` | string | `"deadline-receipt-request"` |
| `otherDeadlines` | array | `[]` |
| `submissionTimes` | array | `[]` |
| `contractNature` | array | `["services"]` |
| `procedureType` | string | `"open"` |
| `noticeUrl` | string | `"https://ted.europa.eu/en/notice/-/detail/682380-2026"` |
| `pdfUrl` | string | `"https://ted.europa.eu/en/notice/682380-2026/pdf"` |
| `xmlUrl` | string | `"https://ted.europa.eu/en/notice/682380-2026/xml"` |
| `source` | string | `"TED"` |
| `scrapedAt` | string | `"2026-10-05T12:27:09.699682+00:00"` |
| `errors` | array | `[]` |

Missing values remain null or empty arrays. A notice's title or buyer can use another published language when the requested one is unavailable. No machine translation or AI enrichment is performed.

TED's `total-value` can describe an estimate, an award or a framework amount. `valueSource` identifies the selected indexed field; it does not certify what was awarded. Choose `estimated-value-proc` for the separately published procedure estimate. These distinctions follow the [official search field list](https://docs.ted.europa.eu/ODS/latest/reuse/field-list.html).

### Input example

```json
{
  "keyword": "software",
  "countries": [
    "FRA"
  ],
  "maxItems": 10
}
```

Use the input schema for the remaining filters and defaults.

### Output example

This is a selected-field excerpt of the first object in [sample_output.json](sample_output.json).

```json
{
  "noticeId": "682380-2026",
  "titleLanguage": "eng",
  "noticeType": "can-standard",
  "publicationDate": "2026-10-05",
  "buyerName": "Mairie d'Asnières sur Seine",
  "buyerNames": [
    "Mairie d'Asnières sur Seine"
  ],
  "buyerCountries": [
    "FRA"
  ],
  "buyerCities": [
    "Asnières sur seine"
  ],
  "buyerWebsites": [],
  "cpvCodes": [
    "72000000",
    "72413000",
    "72267100",
    "72600000",
    "72415000",
    "72317000"
  ],
  "value": 140444.52,
  "currency": "EUR",
  "valueCurrencies": [
    "EUR"
  ],
  "valueSource": "total-value"
}
```

### Pricing

Proposed price: **$0.002 per `notice`**, or **$2 per 1,000 billable notices**, with no additional actor-start or dataset-item surcharge. A billable notice has a usable title or buyer; a warning about another field does not make it free. Error-only rows, empty searches and within-run duplicates have no notice charge. See [PRICING.md](PRICING.md) for dated Store comparisons, event definitions and the publication configuration. Cloud platform billing depends on the owner's settings.

### Limits and failure behavior

- `maxItems` caps output rows including errors. API candidates are also bounded by `maxItems`, so duplicates can leave the dataset below the requested cap. Deduplication does not persist between separate runs.
- TED requests are sequential, use a 30-second request timeout and at most three attempts for transport errors or HTTP 408/429/500/502/503/504. Backoff is bounded at 30 seconds and respects a bounded Retry-After value.
- The actor uses [TED scroll/iteration mode](https://docs.ted.europa.eu/ODS/latest/reuse/search-api.html). Broad queries can still time out or encounter upstream availability/field limits. A failed search adds an uncharged error row and fails the run; earlier successful rows remain saved.
- Malformed individual notices produce uncharged error rows and processing continues. Missing amounts and deadlines are normal source omissions, especially on award notices.
- Submission deadlines prefer `deadline-receipt-request`, then the published tender date. Generic `deadline` values are retained separately and never substituted as submission deadlines. Confirm timing and lot terms in the linked official notice before bidding.
- Dates and timestamps retain the published precision; the actor does not reconstruct lot relationships. No attachments, national portal crawling, contact enrichment, login or CAPTCHA processing are included.
- Results are notice publications rather than unique procurement procedures; corrections or subsequent awards can have different notice IDs.

### Use with AI agents / MCP

An AI agent can call a published actor through the Apify MCP server or Apify API, pass the JSON input, then read the run’s default dataset. Check the actor’s published identifier and permissions in your Apify account.

After the owner publishes and configures this actor, connect your agent to the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) at `https://mcp.apify.com`. Running actors requires Apify authentication; TED itself requires no key.

Use `fetch-actor-details` with the published actor ID, then `call-actor` with the structured input above. Fetch the returned dataset with `get-dataset-items`; `call-actor` returns run/storage information, not the notice rows themselves. The output schema exposes a Results dataset link. Ask the agent to cite `noticeUrl`, distinguish awards from competitions using `noticeType`, and retain null amounts/deadlines instead of inventing them. Start with a small `maxItems` and a charge limit.

### FAQ

**What limits the output?** The maxItems input caps output rows; its schema maximum is 100,000. Source availability and errors may yield fewer rows.

**Does it need a proxy, login, or API key?** The actor calls the public TED API without a TED key or proxy input. An Apify credential is needed to run a private actor through the platform.

**How is it priced?** PRICING.md proposes $0.002 per unique usable notice saved within a run. Error and duplicate rows have no notice event; the proposal is unpublished.

**What happens on rate limits?** The TED client retries throttling and transient server failures with Retry-After when supplied. Exhausted failures are reported.

**How fresh is the data?** Rows reflect TED at run time. publicationDate is the notice date; scrapedAt records retrieval.

**Does a publication date mean bidding is still open?** No. Inspect noticeType, deadline, and the linked TED notice.

### Local development and measured checks

Use the existing `C:/GritWork/earn/.venv/Scripts/python.exe`; do not install packages into the shared environment. Runtime requirements are `apify` and `httpx`.

From this actor directory in PowerShell, set local storage, save the input example as `storage/key_value_stores/default/INPUT.json`, then run:

```powershell
$env:APIFY_LOCAL_STORAGE_DIR = "$PWD/storage"
& C:/GritWork/earn/.venv/Scripts/python.exe -m src
& C:/GritWork/earn/.venv/Scripts/python.exe -m pytest -q
apify validate-schema
```

Ordinary local runs do not charge a real account. To reproduce local billing simulation and the acceptance probes, run `C:/GritWork/earn/.venv/Scripts/python.exe evidence/local_run.py`, then `C:/GritWork/earn/.venv/Scripts/python.exe evidence/verify_outputs.py`. The harness supplies explicit prices and zero starting charge counts; test logs are stored under `evidence/`. Unit tests use saved real responses; the marked `live` smoke test contacts TED.

Observed on 2026-10-05: **59 tests passed, including the live smoke test**. The sample query saved **10 notices in 2.015 seconds of local process wall time** (1.313 seconds inside the initialized actor). These are one local run's measurements, not a throughput guarantee. [Evidence](evidence/local-runs.json) includes changed-country, empty/invalid-input, duplicate, malformed-response and budget probes; twelve saved notices were independently compared with TED.

# Actor input Schema

## `keyword` (type: `string`):

Literal full-text phrase in TED's indexed notice text. Quotes are escaped; expert-query fragments are not supported.

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

Buyer country, not place of performance. ISO-3 codes such as DEU and FRA; EU/EEA ISO-2 codes plus CH, GB, UK and TR are also accepted.

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

2-8 digits; 72 matches the IT branch and 72000000 matches that exact code. Optional check digit accepted for full codes, e.g. 72000000-5.

## `noticeTypes` (type: `array`):

TED codes, e.g. cn-standard (competition) or can-standard (result). Empty includes all types, including awards and corrections.

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

Inclusive YYYY-MM-DD publication date. Empty means no lower date bound.

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

Inclusive YYYY-MM-DD publication date. Empty means no upper date bound.

## `minValue` (type: `number`):

Inclusive minimum in the selected currency/field. Notices missing that field will not match.

## `maxValue` (type: `number`):

Inclusive maximum in the selected currency/field; no currency conversion.

## `valueCurrency` (type: `string`):

Three-letter code. Applied when minValue or maxValue is set. Empty removes the currency filter; amounts may then use different currencies.

## `valueField` (type: `string`):

Controls both the numeric search filter and output value. TED total-value may represent an estimate, award or framework amount; consult valueSource and noticeType.

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

ISO-3 language for title, buyer display and links. Missing translations fall back to English, multilingual text, then another published language; no machine translation.

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

Hard cap on dataset rows including errors. Duplicates are skipped within this run. API candidates are also capped, so duplicates may yield fewer rows.

## `pageSize` (type: `integer`):

Notices requested per scroll page. Requests run sequentially, with one request in flight, to respect TED and payment limits.

## Actor input object example

```json
{
  "keyword": "software",
  "countries": [],
  "cpvCodes": [],
  "noticeTypes": [],
  "valueCurrency": "EUR",
  "valueField": "total-value",
  "language": "eng",
  "maxItems": 100,
  "pageSize": 100
}
```

# Actor output Schema

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

Download the default dataset notice rows as JSON, or export in Apify Console.

# 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 = {
    "keyword": "software"
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/eu-public-tenders-ted-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 = { "keyword": "software" }

# Run the Actor and wait for it to finish
run = client.actor("grit-77/eu-public-tenders-ted-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 '{
  "keyword": "software"
}' |
apify call grit-77/eu-public-tenders-ted-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/eu-public-tenders-ted-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/UDjshwgwmBDPXFzVL/builds/ifZrLXBdFBOhrDiFw/openapi.json
