# TED EU Tenders Monitor - Public Procurement Alerts (`lukehunter/ted-eu-tenders-monitor`) Actor

Monitor new EU public tenders from the official TED API. Filter by CPV code, country, value, keyword and buyer, get only new notices on a schedule, and alert Slack or Discord for $0.005/notice.

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

## Pricing

$5.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

## TED EU Tenders Monitor - Public Procurement Alerts

**For bid teams, consultancies, suppliers and sales teams who sell to European public buyers.** Get new **TED tenders** and **EU tenders** from the official **TED API** (Tenders Electronic Daily, the EU's public procurement journal) in one clean dataset. Filter by CPV code, buyer country, contract value, keyword and buyer, return only notices you have not seen before on a schedule, and send each new one to Slack, Discord, Telegram or your phone. Built for **public procurement Europe** monitoring without copying notices by hand.

**$0.005 per delivered notice — 1,000 notices = $5**

It reads the official, public **TED Search API v3** (`api.ted.europa.eu`). No scraping, no login, no proxy, no API key. Only notices that pass your filters are delivered, and only delivered notices are charged. Selling to the UK as well? See the sibling [UK Tenders Monitor](https://apify.com/lukehunter/uk-tenders-monitor) (Contracts Finder and Find a Tender): same inputs, same alerts.

### What you get

One row per notice, flattened and ready for a spreadsheet, CRM or alert: **publication number**, notice type, **title (English when TED has it, otherwise the original language with its language code)**, buyer name and country, publication date, **bid deadline**, **estimated value and currency**, CPV codes, procedure type, place of performance, the **TED notice page link**, a link to the tender documents, and the number of lots.

| You want | Set |
|---|---|
| IT tenders in Germany over €200k | `cpvCodes: ["72"]`, `countries: ["DE"]`, `minValue: 200000` |
| Anything mentioning a topic, in any EU language | `keywords: ["cybersecurity", "Cybersicherheit"]` |
| One buyer family | `buyerContains: "ministry"` |
| Several countries | `countries: ["IE", "NL", "BE"]` |
| Who won what (competitor research) | `noticeTypes: ["award"]` |
| Early warning of future procurement | `noticeTypes: ["planning"]` |
| Only what is new since yesterday | `onlyNewSinceLastRun: true` on a daily Schedule |

### Quick start

```json
{
  "cpvCodes": ["72"],
  "publishedWithinDays": 7,
  "maxItems": 20
}
```

Run it with no filters at all and you get the 50 newest contract notices (open tenders) from the last 7 days across Europe.

### Recipes

**IT tenders in Germany over €200k to Slack**

```json
{
  "cpvCodes": ["72"],
  "countries": ["DE"],
  "minValue": 200000,
  "noticeTypes": ["competition"],
  "onlyNewSinceLastRun": true,
  "maxItems": 50,
  "slackWebhookUrl": "https://hooks.slack.com/services/<T>/<B>/<token>"
}
```

Save it as a Task, add a daily Schedule, and Slack gets only the German IT services tenders published since the last run. The first run is the baseline and sends what was published in the window.

**Daily EU tender alerts by CPV**

```json
{
  "cpvCodes": ["72", "48", "79"],
  "publishedWithinDays": 2,
  "onlyNewSinceLastRun": true,
  "discordWebhookUrl": "https://discord.com/api/webhooks/<id>/<token>"
}
```

**Construction tenders in France, Belgium and Luxembourg**

```json
{
  "cpvCodes": ["45"],
  "countries": ["FR", "BE", "LU"],
  "publishedWithinDays": 30,
  "maxItems": 200
}
```

**Last quarter's awards for one category (competitor and pricing research)**

```json
{
  "noticeTypes": ["award"],
  "cpvCodes": ["72"],
  "publishedFrom": "2026-07-13",
  "publishedTo": "2026-10-10",
  "maxItems": 500
}
```

### Alerts: Discord, Telegram, Slack, Teams, Google Chat, phone push or any app

Add one or more destinations in the **Notifications (optional)** section and each run sends the notices it delivered. It is a monitor that notifies you: it only reads the public TED API, and never bids, applies, contacts a buyer or logs in anywhere.

| Channel | Fill in | What you get |
|---|---|---|
| Discord | `discordWebhookUrl` | One card per notice, 10 per message |
| Telegram | `telegramBotToken` + `telegramChatId` | Up to 10 notices per message, one line each |
| Slack | `slackWebhookUrl` | Block Kit message, up to 10 notices per message |
| Mattermost, Rocket.Chat | `slackCompatibleWebhookUrl` | A Markdown list |
| Microsoft Teams | `teamsWebhookUrl` | Adaptive Card from a Teams Workflows webhook |
| Google Chat | `googleChatWebhookUrl` | Card in a space |
| ntfy (phone push, no account) | `ntfyTopic` (optional `ntfyServer`, `ntfyToken`) | A push per notice, up to 10, then one "...and N more" |
| Pushover (phone push) | `pushoverUserKey` + `pushoverAppToken` | A push per notice, up to 10, then one "...and N more" |
| Zapier, Make, n8n, Pipedream | `webhookUrl` | One JSON POST per 100 delivered rows with the full dataset rows |

Each message reads like `New tender: Cloud hosting and managed services — Stadtwerke Beispielstadt GmbH`, with the value, closing date, published date, country, CPV codes, lots and procedure, and a link to the TED notice. Nothing is sent when a run delivers nothing, a failing channel never fails the run, and notifications are free: you pay only for delivered notices. Webhook URLs and tokens are stored as secret inputs.

Setup per channel is a minute or two: for Discord copy a channel webhook URL (Server Settings → Integrations → Webhooks); for Telegram create a bot with @BotFather and give its token plus a chat id; for Slack create an incoming webhook at api.slack.com/apps; for Teams use a Workflows "Post to a channel when a webhook request is received" URL.

`maxNotifications` (default 25, max 100) caps how many notices go to chat and phone channels per run; the rest are still in the dataset. Use `onlyNewSinceLastRun` for alerts, otherwise a scheduled run repeats the same notices.

### Input

| Field | Default | What it does |
|---|---:|---|
| `keywords` | `[]` | TED full-text search: notices matching any of these words or phrases. Works across EU languages. No quotes or brackets inside a word; up to 20. |
| `cpvCodes` | `[]` | CPV prefix match, searched by TED. `72` = all IT services, `72200000-7` = software programming. Invalid codes stop the run with an error. |
| `countries` | `[]` | Buyer country, two- or three-letter ISO code (`DE` or `DEU`). `EL`/`GR` = Greece, `UK`/`GB` = United Kingdom. Empty = all. |
| `noticeTypes` | `["competition"]` | `competition` (contract notices you can bid for; aliases `tender`, `opportunity`), `award` (contract award results; alias `result`), `planning` (prior information; alias `prior-information`). |
| `minValue`, `maxValue` | none | Euro bounds on the estimated value. Notices with no published value, or in another currency, are left out while either bound is set. No exchange rates are applied. |
| `buyerContains` | `""` | Buyer name contains this text (case-insensitive, matched on the name as published). |
| `publishedWithinDays` | `7` | 1 to 90 days back, including today. |
| `publishedFrom`, `publishedTo` | none | Explicit `YYYY-MM-DD` range, at most 90 days. Overrides `publishedWithinDays`. |
| `maxItems` | `50` | Hard cap on delivered notices, 1 to 2,000, newest first. |
| `onlyNewSinceLastRun` | `false` | Deliver and charge only notices not delivered before for this same set of filters. |
| `stateStoreName` | `ted-eu-tenders-monitor-seen-notices` | Named store holding the alert memory. |
| notification fields | none | `discordWebhookUrl`, `telegramBotToken`, `telegramChatId`, `slackWebhookUrl`, `slackCompatibleWebhookUrl`, `teamsWebhookUrl`, `googleChatWebhookUrl`, `ntfyTopic`, `ntfyServer`, `ntfyToken`, `pushoverUserKey`, `pushoverAppToken`, `webhookUrl`, `maxNotifications`. |

Keywords, CPV codes, countries, notice types and dates are turned into one TED expert-search query and run by TED itself. The value range and the buyer name are checked after download, so with a broad search they can make the run read more pages (the run stops at 60 pages or 15 minutes and says so in its status message).

#### How "only new" works

With `onlyNewSinceLastRun`, the Actor remembers each delivered notice by its TED publication number, in a named key-value store, per set of filters (changing a keyword starts a fresh baseline; changing `maxItems` or `publishedWithinDays` does not). Only notices actually delivered are remembered, so anything held back by `maxItems` or your spending limit turns up on the next run. A contract notice and its later award have different publication numbers, so the award alerts separately.

### Example output

```json
{
  "publicationNumber": "612345-2026",
  "noticeType": "competition",
  "noticeSubtype": "cn-standard",
  "title": "Cloud hosting and managed services",
  "titleLanguage": "eng",
  "buyerName": "Stadtwerke Beispielstadt GmbH",
  "buyerCountry": "DEU",
  "publicationDate": "2026-10-09",
  "deadlineDate": "2026-11-10",
  "estimatedValue": 270000,
  "currency": "EUR",
  "cpvCodes": ["72000000", "72500000"],
  "procedureType": "open",
  "placeOfPerformance": "DEU",
  "url": "https://ted.europa.eu/en/notice/-/detail/612345-2026",
  "documentsUrl": "https://vergabe.example/docs/612345",
  "lots": 2,
  "scrapedAt": "2026-10-11T09:00:00.000Z"
}
```

This sample is illustrative (synthetic buyer and domain names), shaped like TED's search results. Anything a notice does not publish is `null`, never guessed. `title` is English when TED has an English title, otherwise the original-language text with `titleLanguage` set to its code (`deu`, `fra`, `pol`). `estimatedValue` is the notice total when TED publishes one, otherwise the sum of the lot estimates when they share one currency; `deadlineDate` is the earliest lot deadline. `buyerCountry` is TED's ISO alpha-3 code. Awards and planning notices usually have no deadline.

### Use with AI agents (MCP)

This Actor works as a tool for Claude, ChatGPT, Cursor and other MCP clients through [Apify's MCP server](https://mcp.apify.com). Every input is optional, so a call can be as small as `cpvCodes` and `publishedWithinDays`.

**Claude Desktop** (`claude_desktop_config.json`), with your own token from Apify Console > Settings > API & Integrations:

```json
{
  "mcpServers": {
    "apify-ted-tenders": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://mcp.apify.com?tools=lukehunter/ted-eu-tenders-monitor",
        "--header", "Authorization: Bearer ${APIFY_TOKEN}"
      ],
      "env": { "APIFY_TOKEN": "<YOUR_APIFY_TOKEN>" }
    }
  }
}
```

Clients that support OAuth (Claude.ai, VS Code) can connect with just the URL `https://mcp.apify.com?tools=lukehunter/ted-eu-tenders-monitor` and sign in to Apify. Never paste a real token into a shared file or chat.

**Example.** You ask: "Which German public bodies published IT tenders worth over €200k this week, and when do they close?" The agent calls the tool `lukehunter--ted-eu-tenders-monitor` with:

```json
{
  "cpvCodes": ["72"],
  "countries": ["DE"],
  "minValue": 200000,
  "publishedWithinDays": 7,
  "maxItems": 25
}
```

It is charged $0.005 per delivered notice, capped by `maxItems`. Each input property description is short and carries an example, so agents fill it in correctly.

### Pricing

Pay-per-event: **$0.005 per delivered notice.** A notice is charged only after it is accepted into the dataset, and only if it passed your filters (and, in alert mode, was not delivered before).

| Notices delivered | Cost |
|---:|---:|
| 20 | $0.10 |
| 50 (default cap) | $0.25 |
| 2,000 (maximum) | $10.00 |

`maxItems` (hard maximum 2,000) and your Apify per-run spending limit both stop delivery before the cap is passed. Reading TED, filtering and sending notifications cost nothing extra. Price changes follow Apify's notice rules; the Pricing tab always shows the rate you are charged.

### Limitations

- **EU open data, attribution required.** TED notices are public data of the European Union, free to reuse under the EU Publications Office's reuse policy. If you republish them, credit the source, for example "Source: TED, European Union, https://ted.europa.eu", and see the TED legal notice for the exact terms.
- **Not affiliated with the EU.** This is an independent Actor reading the public TED API. Always confirm deadlines and requirements on the official TED notice page (the `url` field) before bidding.
- **Coverage is what TED publishes.** TED carries notices above the EU thresholds plus some national ones; many below-threshold tenders are only on national portals, which this Actor does not read.
- **Values are often missing, and never converted.** Many notices publish no value, and values come in the buyer's currency. With `minValue` or `maxValue` set, notices without a euro value are left out; leave the bounds empty to see them with `null` values or another currency.
- **Titles and buyer names are in the notice language** when no English version exists; `titleLanguage` says which.
- **Time window, not archive.** Each run covers at most 90 days and reads at most 60 pages (up to 15,000 notices) over at most 15 minutes. When a window is bigger than that, the run says it stopped early; narrow the dates or add filters.
- **TED query changes.** TED's field names and query syntax are versioned by the Publications Office. If TED rejects an optional field the Actor drops that column and says so in the status message; if it rejects the search itself, the run fails with TED's message and nothing is charged.
- **Rate limits.** Requests are paced at about one a second, and 429/5xx responses are retried with backoff. If TED stays unavailable, nothing is charged and the run fails with a clear message.

### Use it from your code, Make, Zapier or n8n

Built for automated pipelines: small runs, stable field names, one row per item, and you pay only for rows actually delivered.

**One API call that runs the Actor and returns the data** (replace `$APIFY_TOKEN` with your token from Apify Console → Settings → Integrations):

```bash
curl -s -X POST "https://api.apify.com/v2/acts/lukehunter~ted-eu-tenders-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"keywords": ["software"], "cpvCodes": ["72"], "publishedWithinDays": 7, "maxItems": 20}'
```

**JavaScript** (`npm i apify-client`):

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lukehunter~ted-eu-tenders-monitor').call({
  "keywords": [
    "software"
  ],
  "cpvCodes": [
    "72"
  ],
  "publishedWithinDays": 7,
  "maxItems": 20
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

**Python** (`pip install apify-client`):

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('lukehunter~ted-eu-tenders-monitor').call(run_input={"keywords": ["software"], "cpvCodes": ["72"], "publishedWithinDays": 7, "maxItems": 20})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

**Schedules, webhooks and no-code tools:** save your input as a Task, add a Schedule (e.g. hourly or daily), and attach a webhook or one of the Integrations tab's connectors (Make, Zapier, n8n, Google Sheets, Slack) to push each run's results into your own system.

**Only new items each run:** set `"onlyNewSinceLastRun": true` on a scheduled Task and every run after the first delivers, and charges for, only items it hasn't delivered before for that same input. That makes it a ready-made change feed.

**Feedback:** an honest rating on the Store page, good or bad, helps others decide if this Actor fits their needs. Missing a field or found a problem? Open an issue on the Actor page.

### FAQ

**Is this the official TED API?** Yes. It calls the public TED Search API v3 that the EU Publications Office provides, anonymously, with an honest User-Agent and no proxy.

**What is a CPV code?** The Common Procurement Vocabulary is the EU classification of what is being bought. `72` is IT services, `45` construction, `33` medical equipment, `79` business services. Enter any 2 to 8 digits as a prefix.

**Why is a notice I saw on the TED website missing?** Check its type (the default is `competition` only), its publication date against your window, and whether `minValue`/`maxValue` excluded it for having no euro value.

**Does it cover the UK?** TED no longer carries UK tenders after Brexit, except Northern Ireland under the Windsor Framework. Use the [UK Tenders Monitor](https://apify.com/lukehunter/uk-tenders-monitor) for Contracts Finder and Find a Tender.

**Can I get tenders in Slack, Discord or Telegram every morning?** Yes: set `onlyNewSinceLastRun`, add a destination and a daily Schedule. See the recipes above.

**Does it submit bids or contact buyers?** No. It only reads and reports.

### Related Actors

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.

- **[Federal Register Rule Effective-Date Radar](https://apify.com/lukehunter/federal-rule-effective-date-radar)**: final federal rules about to take effect, with citations, dockets and urgency.
- **[UK Tenders Monitor](https://apify.com/lukehunter/uk-tenders-monitor)**: new UK public-sector tenders from Contracts Finder and Find a Tender, filtered by CPV code, value, region and buyer, with Slack, Discord and Telegram alerts.
- **[Chicago Contract Recompete & Extension Radar](https://apify.com/lukehunter/chicago-contract-expiration-radar)**: expiring City of Chicago contracts with extension history, for recompete prospecting.
- **[Bank Branch Closings & Merger Radar (FDIC)](https://apify.com/lukehunter/bank-branch-merger-radar)**: new US bank branch closings, openings and mergers from the FDIC.
- **[Chicago New Business License Radar](https://apify.com/lukehunter/chicago-new-business-license-radar)**: newly licensed Chicago businesses as opening leads, grouped by category.
- **[NYC FDNY Contractor Leads & Hearing Radar](https://apify.com/lukehunter/fdny-violation-radar)**: fresh NYC fire-code summonses routed by remediation trade, with hearing dates.
- **[RCRA Enforcement Response Leads](https://apify.com/lukehunter/rcra-enforcement-radar)**: new EPA hazardous-waste (RCRA) enforcement actions and penalties, by state.
- **[Emerging Federal Contractor Radar](https://apify.com/lukehunter/emerging-federal-contractor-radar)**: companies that just won their first federal contracts, from USAspending.gov.

# Actor input Schema

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

Full-text search across the notice, matching at least one of these words or phrases, in any EU language, e.g. \["cloud migration", "cybersecurity", "software"]. No quotes or brackets inside a word. Up to 20. Leave empty to keep every notice that passes the other filters.

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

Keep notices with at least one CPV code starting with these digits. 2 to 8 digits, optional check digit, e.g. 72 (all IT services), 72200000-7 or 45 (construction). An invalid code stops the run with an error. Leave empty for any category.

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

Only notices from buyers in these countries, as two- or three-letter ISO codes, e.g. \["DE", "FR", "IE"] or \["DEU"]. EL and GR both mean Greece, UK and GB the United Kingdom. An unknown code stops the run with an error. Leave empty for all of Europe.

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

Which notices to return, e.g. \["competition", "award"]. competition = contract notices you can still bid for (aliases tender, opportunity); award = contract award results; planning = prior information notices announcing future tenders. Default is competition only.

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

Keep notices whose estimated value is at least this many euros, e.g. 200000. Notices with no published value, or a value in another currency, are left out while this or Maximum value is set (no exchange rates are applied). Leave empty for no lower bound.

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

Keep notices whose estimated value is at most this many euros, e.g. 5000000. Notices with no published value, or a value in another currency, are left out while this or Minimum value is set. Leave empty for no upper bound.

## `buyerContains` (type: `string`):

Keep notices whose buyer name contains this text, case-insensitive, e.g. ministry or Stadtwerke. Matched after download in the buyer's name as published, so with a broad search it can read many pages. Leave empty for any buyer.

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

Only notices published in the last N days including today, 1 to 90, e.g. 7 for the last week. Ignored when Published from is set. Longer windows read more pages from TED and take longer.

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

First day of an explicit range, written YYYY-MM-DD, e.g. 2026-09-01. Use with Published to (defaults to today). The range may not exceed 90 days; a longer one stops the run with an error. Overrides Published within days.

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

Last day of an explicit range, written YYYY-MM-DD, e.g. 2026-09-30. Needs Published from. Leave empty to include everything up to today.

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

Hard cap on delivered notices, newest first, 1 to 2000, e.g. 50. At $0.005 per delivered notice this caps the result charge for the run (50 notices = $0.25). Notices beyond the cap are not charged.

## `onlyNewSinceLastRun` (type: `boolean`):

Deliver and charge only notices not delivered before for this same set of filters, e.g. on a daily Schedule. The first run creates the baseline and delivers what currently matches. Pair with a Discord, Slack or Telegram destination below.

## `stateStoreName` (type: `string`):

Named key-value store that remembers delivered notices between runs, used only when alerts are on. Example: my-it-tenders. Use a different name to keep separate histories. Letters, numbers and hyphens only.

## `discordWebhookUrl` (type: `string`):

Post results to a Discord channel. In Discord: Server Settings -> Integrations -> Webhooks -> New Webhook -> choose the channel -> Copy Webhook URL. Example: https://discord.com/api/webhooks/<id>/<token>. Leave empty to skip Discord.

## `telegramBotToken` (type: `string`):

Post results to Telegram. Create a bot by chatting with @BotFather on Telegram (send /newbot) and paste the token it gives you, e.g. 123456789:AA.... Needs "Telegram chat ID" below. Leave empty to skip Telegram.

## `telegramChatId` (type: `string`):

Where the bot should post: a numeric chat id (e.g. 123456789, or -1001234567890 for a group or channel) or a public @channelusername. The bot must be added to that chat or channel first (as an administrator for a channel), and for a private chat you must press Start on the bot. Needs "Telegram bot token" above.

## `slackWebhookUrl` (type: `string`):

Post results to a Slack channel (Block Kit cards, up to 10 per message). Create an incoming webhook at api.slack.com/apps -> Create New App -> Incoming Webhooks -> Add New Webhook to Workspace. Example: https://hooks.slack.com/services/T000/B000/XXXX. Leave empty to skip Slack.

## `slackCompatibleWebhookUrl` (type: `string`):

Post results to a Mattermost or Rocket.Chat incoming webhook, which accept Slack-style webhook payloads. Must be a public https:// address, e.g. https://chat.example.com/hooks/abc123 (no localhost, private IP or .local/.internal hosts). These apps do not render Slack Block Kit, so they get a plain Markdown list. For real Slack use "Slack webhook URL" above. Leave empty to skip.

## `teamsWebhookUrl` (type: `string`):

Post results to a Teams channel as an Adaptive Card. In Teams: channel -> ... -> Workflows -> "Post to a channel when a webhook request is received" -> copy the URL. The host must end in .logic.azure.com or .powerplatform.com, e.g. https://prod-12.westeurope.logic.azure.com/workflows/... The retired Office 365 connector webhooks are not supported. Leave empty to skip Teams.

## `googleChatWebhookUrl` (type: `string`):

Post results to a Google Chat space. In the space: Apps & integrations -> Webhooks -> Add webhook -> copy the URL. Example: https://chat.googleapis.com/v1/spaces/<space>/messages?key=...\&token=.... Leave empty to skip Google Chat.

## `ntfyTopic` (type: `string`):

Get a phone push with no account via ntfy (ntfy.sh). Install the ntfy app, subscribe to a topic name, and enter the same name here. Anyone who knows the topic can read it, so pick a long, unguessable name (letters, numbers, - and \_), e.g. acme-leads-7k2x9q. At most 10 individual pushes per run, then one "...and N more". Leave empty to skip ntfy.

## `ntfyServer` (type: `string`):

Only used with "ntfy topic". Leave as https://ntfy.sh, or enter the public https:// address of your own ntfy server, e.g. https://ntfy.example.com.

## `ntfyToken` (type: `string`):

Only used with "ntfy topic", for a self-hosted server or a reserved topic that needs a login. An access token from ntfy (starts with tk\_, e.g. tk_abc123...). Leave empty for open topics.

## `pushoverUserKey` (type: `string`):

Get a phone push via Pushover. Your user key (30 characters, e.g. uQiRzpo4DXghDmr9QzzfQu27cmVRsG) is shown on the Pushover dashboard (pushover.net). Needs "Pushover app token" below. At most 10 individual pushes per run, then one "...and N more". Leave empty to skip Pushover.

## `pushoverAppToken` (type: `string`):

The API token of a Pushover application you create at pushover.net/apps/build (name it after this Actor), e.g. azGDORePK8gMaC0QOYAMyEEuzJnyUi. Needs "Pushover user key" above.

## `webhookUrl` (type: `string`):

POST the delivered rows as JSON to any public https:// URL: {source, actor, runId, count, items}. At most 100 rows per POST; every delivered row is sent regardless of the max-notifications cap. For example, route results to email, a spreadsheet or any app. Localhost, private IP and .local/.internal addresses are rejected. Leave empty to skip.

## `maxNotifications` (type: `integer`):

Caps how many results are sent to chat and phone channels per run (Discord, Telegram, Slack, Teams, Google Chat, ntfy, Pushover), 1-100, default 25 (e.g. 10). Extra results are still in the dataset. The generic webhook is not capped. Only used when a destination above is set.

## Actor input object example

```json
{
  "keywords": [
    "software"
  ],
  "cpvCodes": [
    "72"
  ],
  "noticeTypes": [
    "competition"
  ],
  "publishedWithinDays": 7,
  "maxItems": 20,
  "onlyNewSinceLastRun": false,
  "stateStoreName": "ted-eu-tenders-monitor-seen-notices",
  "ntfyServer": "https://ntfy.sh",
  "maxNotifications": 25
}
```

# Actor output Schema

## `tenders` (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"
    ],
    "cpvCodes": [
        "72"
    ],
    "publishedWithinDays": 7,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("lukehunter/ted-eu-tenders-monitor").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"],
    "cpvCodes": ["72"],
    "publishedWithinDays": 7,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("lukehunter/ted-eu-tenders-monitor").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"
  ],
  "cpvCodes": [
    "72"
  ],
  "publishedWithinDays": 7,
  "maxItems": 20
}' |
apify call lukehunter/ted-eu-tenders-monitor --silent --output-dataset

```

## MCP server setup

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

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/HvbdMZDYD3DM7pcfU/builds/PUioO5rhATBkUayAB/openapi.json
