# Nordic Tender Monitor: SE, NO, FI, DK, new only (`nordic_signals/nordic-tender-monitor`) Actor

- **URL**: https://apify.com/nordic_signals/nordic-tender-monitor.md
- **Developed by:** [Nordic Signals](https://apify.com/nordic_signals) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 new 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 Tenders Monitor – Nordic Public Procurement (SE, NO, FI, DK)

**Get only the new public tenders from Sweden, Norway, Finland and Denmark, filtered on the CPV codes and keywords you care about, in one clean format.** Built for companies that bid on public contracts and for the consultants and bid writers who monitor tenders for them. Schedule it daily and you get a short list of fresh opportunities instead of a morning of searching national portals.

### What it does: monitor Nordic public tenders from TED

The actor asks the official search API of TED (Tenders Electronic Daily, run by the Publications Office of the European Union) for contract notices published by buyers in the countries you pick. Norway is covered because it is part of the EEA. By default it keeps only notices where a buyer is asking for bids; switch on contract awards to also (or only) get the notices that say who won. It filters on your CPV codes, keywords, buyer names, place of performance, value and time left to bid, and remembers what it has already delivered. The next run returns only what is new since the last one.

Every notice has the same fields whatever the country: buyer, title, CPV codes, publication date, deadline for tenders, estimated value with currency, procedure type, number of lots, place of performance, a link to the procurement documents where TED has one, and a link to the full notice on TED. Contract awards add the winning company, its country and the awarded value.

### Data source and legal basis (TED open data)

Only the official TED API is used, anonymously and without scraping any portal's own search backend. TED notices are open for commercial and non-commercial reuse under the [TED legal notice](https://ted.europa.eu/en/legal-notice). The output contains organisation names only: no contact persons, no e-mail addresses, no phone numbers. Contact fields are deliberately never requested.

Source: TED (Tenders Electronic Daily), © European Union, https://ted.europa.eu. The data has been processed: notices are filtered, and fields are selected and renamed. The original notice is always linked in `url`; it is the authoritative version.

TED publishes notices above the EU thresholds. Smaller national procurements that only appear on Doffin (Norway), Hilma (Finland) or Danish portals are not included in this version.

### Personal data (GDPR)

Buyers are public bodies, and the actor returns only organisation names. Buyers in these notices are contracting authorities (public bodies and publicly owned companies), not sole traders. Winners of contract awards can occasionally be sole traders, whose business name is a private person's name, so winners are filtered: names that look like a private person or sole trader (such as "Anna Svensson" or names ending in "tmi", "ENK" or "v/") are dropped, and when TED's identifiers show that a winner has a personal identity number, no winner is returned for that notice at all. A contract award left without a winner is not delivered and the notice fee is not charged (the run fee still is). The filter is deliberately cautious, so some genuine companies with a two- or three-word name and no company form (for example "Hitachi Energy") can be left out. It is a best-effort filter on public procurement data, not a guarantee. Contact persons, e-mail addresses and phone numbers are never requested. Every version is checked automatically for e-mail addresses, mobile numbers and Swedish personal identity numbers before it is published. If you still find personal data in a result, open an issue on the Issues tab and it will be removed.

### Input: filter tenders by country, CPV code and keyword

| Field | Meaning | Default |
|---|---|---|
| `countries` | Buyer country: `SE`, `NO`, `FI`, `DK` | all four |
| `cpvCodes` | CPV code or the start of one (2 to 8 digits). `72` matches all IT services, `71310000` matches technical consultancy | empty (all) |
| `keywords` | A notice must contain at least one of these as a whole word or phrase in its short title or buyer name (case-insensitive, so `it` does not match "profiling") | empty (all) |
| `onlyNew` | Turn on for scheduled monitoring: returns only notices not delivered before | `false` |
| `noticeTypes` | `tenders` (calls for tenders, the default) and/or `awards` (contract award notices: who won) | `["tenders"]` |
| `buyerNames` | The buyer's name must contain at least one of these as a whole word or phrase (case-insensitive), e.g. `Region Skåne` | empty (all) |
| `nutsCodes` | NUTS code or the start of one, e.g. `SE11` (Stockholm), `SE22` (Skåne), `NO072`. Notices without a stated place are left out when set | empty (all) |
| `minEstimatedValue` | Only notices worth at least this much, in the notice's own currency (no conversion between SEK, NOK, DKK and EUR). Tenders use the estimated value, awards the awarded value. Notices without a value are left out when set | none |
| `minDaysToDeadline` | Tenders only: skip calls that close in fewer than this many days from today. Tenders without a deadline are left out when set. Awards are not affected | none |
| `daysBack` | How many days of notices to return; with `onlyNew` on, only for the first run | 3 |
| `maxResults` | Cap per run (also caps the cost) | 50 |

Invalid input, such as an unknown country, a CPV code with letters in it or a negative value, stops the run with a clear message before anything is charged.

Example input:

```json
{
  "countries": ["SE", "NO"],
  "cpvCodes": ["72", "48"],
  "keywords": ["molntjänster", "cloud"],
  "onlyNew": true
}
```

Example input for contract awards in Stockholm worth at least 1 million:

```json
{
  "countries": ["SE"],
  "noticeTypes": ["awards"],
  "nutsCodes": ["SE11"],
  "minEstimatedValue": 1000000,
  "daysBack": 14
}
```

Example input for tenders that still have at least two weeks left:

```json
{
  "countries": ["SE", "NO", "FI", "DK"],
  "cpvCodes": ["72"],
  "minDaysToDeadline": 14
}
```

### Output: one notice per row

```json
{
  "id": "700359-2026",
  "country": "SE",
  "buyer": "VARBERGS KOMMUN",
  "short_title": "Teknisk konsult Riskhantering vibrerande arbeten",
  "title": "Sverige – Arkitekt-, bygg-, ingenjörs- och besiktningstjänster – Teknisk konsult Riskhantering vibrerande arbeten",
  "notice_type": "cn-standard",
  "notice_category": "tender",
  "cpv_codes": ["71000000", "71310000"],
  "published_date": "2026-10-12",
  "submission_deadline": "2026-11-10",
  "estimated_value": 5000000,
  "currency": "SEK",
  "winner": null,
  "winners": [],
  "winner_country": null,
  "awarded_value": null,
  "awarded_currency": null,
  "procedure_type": "open",
  "lots_count": 1,
  "place_of_performance": { "nuts": ["SE224"], "city": null },
  "documents_url": "https://tendsign.com/doc.aspx?MeFormsNoticeId=100433",
  "url": "https://ted.europa.eu/en/notice/-/detail/700359-2026",
  "source": "TED (Tenders Electronic Daily), © European Union"
}
```

A contract award has the same fields, with the winner filled in and no deadline:

```json
{
  "id": "704428-2026",
  "country": "SE",
  "buyer": "Nordvästra Skånes Vatten och Avlopp AB",
  "notice_type": "can-standard",
  "notice_category": "award",
  "submission_deadline": null,
  "winner": "Envidan AB",
  "winners": ["Envidan AB", "Norconsult Sverige AB", "Sweco Sverige AB", "WSP Sverige AB", "Ramboll"],
  "winner_country": "SE",
  "awarded_value": 108000000,
  "awarded_currency": "SEK",
  "procedure_type": "open",
  "lots_count": 1,
  "place_of_performance": { "nuts": ["SE224"], "city": null },
  "url": "https://ted.europa.eu/en/notice/-/detail/704428-2026"
}
```

(Shortened: title, CPV codes and the other fields are always present.)

| Field | Meaning |
|---|---|
| `id` | TED publication number |
| `country` | Buyer country (`SE`, `NO`, `FI`, `DK`) |
| `buyer` | Buyer organisation name |
| `short_title` | The notice title without the country and category prefix |
| `title` | Full TED title (Swedish, Finnish or Danish where TED has it; Norwegian notices are mostly in English) |
| `notice_type` | TED notice type, e.g. `cn-standard` (tender) or `can-standard` (contract award) |
| `notice_category` | `tender` or `award` |
| `cpv_codes` | CPV codes of the procurement |
| `published_date` | Publication date (YYYY-MM-DD) |
| `submission_deadline` | Earliest deadline for tenders (YYYY-MM-DD) or `null` |
| `estimated_value` | Estimated value or `null` |
| `currency` | Currency of the estimated value |
| `winner` | Awards only: the winning company (the first one when there are several), otherwise `null` |
| `winners` | Awards only: all winning companies, otherwise an empty list |
| `winner_country` | Awards only: country of the first winner, as a two-letter code for common countries, otherwise TED's three-letter code; `null` when unclear |
| `awarded_value` | Awards only: total awarded value, or `null` |
| `awarded_currency` | Awards only: currency of the awarded value |
| `procedure_type` | TED procedure type, e.g. `open`, `restricted` |
| `lots_count` | Number of lots, or `null` |
| `place_of_performance` | `{ nuts: [...], city }` or `null` when the notice does not state a place |
| `documents_url` | Link to the procurement documents where TED has one, otherwise `null` |
| `url` | Link to the full notice on TED |
| `source` | Attribution |

The estimated value is in the buyer's currency (SEK, NOK, EUR, DKK) and is `null` when the notice does not state one. `submission_deadline` is `null` when no deadline is published and always for contract awards. For awards with several winners TED does not always list names and countries in the same order, so `winner_country` is the country TED lists first.

### Pricing: pay per tender notice

Pay per event: **$0.01 per delivered notice** (event `notice`) and **$0.02 per run** (event `run`). The run fee is charged once TED has answered, even when no notice matches. Apify also adds its own tiny fee for starting an actor.

With `onlyNew` off (the default), a run returns every matching notice from the last `daysBack` days, up to `maxResults`. Example: a run that returns 50 notices costs 50 × $0.01 + $0.02 = **$0.52**. The default (3 days, at most 50 notices) never costs more than $0.52. With `onlyNew` on, a daily schedule that finds three new notices costs about $0.05 per day.

You can also set a maximum cost per run in the Apify Console. The actor then delivers only as many notices as fit within it, and you are never charged for more than you get; with `onlyNew` on, the undelivered notices come back on the next run.

### How often to run it: daily public procurement alerts

Once a day on weekdays is enough: TED publishes new notices every working day. Turn on `onlyNew`, use Apify Schedules or the API, and connect the dataset to e-mail, Slack, Make or Zapier with an integration.

Don't run overlapping schedules of the same watch. Each watch (the same countries, CPV codes and keywords) has its own memory of what it has delivered; two runs of the same watch at the same time can both deliver, and charge for, the same notice. Separate watches with different filters can run side by side.

### Get results in Slack, Google Sheets or e-mail

The actor is built for a daily watch: it returns only notices you have not seen before and your tools do the rest. Setting it up takes a few minutes in Apify Console.

**1. Save your watch as a Task.** Open the actor, enter your countries, CPV codes and keywords, turn on `onlyNew`, and click **Save as a new task** (top right). The task keeps the input. See [Tasks](https://docs.apify.com/platform/actors/running/tasks).

**2. Schedule it.** In Console go to **Schedules**, click **Create new**, set the cron expression `0 7 * * 1-5` (weekdays at 07:00), click **Add**, choose **Task** and select your task. New schedules start disabled, so click **Enable** when you are done, and check the time zone of the cron expression. See [Schedules](https://docs.apify.com/platform/schedules). Keep one schedule per watch, as described under "How often to run it".

**3. Send the results where you work.** Open the task's **Integrations** tab and add one of these:

- **Slack:** posts a message to a channel when a run succeeds. The message is a template you can edit; by default it reports the run status, and you can add a link to the dataset. See [Slack integration](https://docs.apify.com/integrations/slack).
- **E-mail:** the Gmail integration sends an e-mail after each run, with the dataset attached if you choose. It sends from your own Google account. See [Gmail integration](https://docs.apify.com/integrations/gmail).
- **Google Sheets:** use [Make](https://docs.apify.com/integrations/make), whose Apify module can watch finished runs and fetch dataset items for a Google Sheets step. [Zapier](https://docs.apify.com/integrations/zapier) and [n8n](https://docs.apify.com/integrations/n8n) work the same way. Apify's Google Drive integration can also upload each run's dataset as a file to your Drive ([docs](https://docs.apify.com/integrations/drive)).
- **Webhook:** Apify sends an HTTP POST to your URL when the run finishes, so your own system can fetch the dataset. See [Webhooks](https://docs.apify.com/integrations/webhooks).

Choose the trigger events when you add an integration: Slack lets you pick events such as run succeeded or failed, and the Gmail and Google Drive integrations send after successful runs by default. Run the task once to test.

**What a typical watch costs.** A weekday schedule is about 22 runs a month. If each run finds three new notices, a run costs 3 × $0.01 + $0.02 = $0.05, so about **$1.10 a month** plus Apify's small start fee. A busier watch that finds ten new notices a run costs $0.12 a run, about **$2.64 a month**. A run that finds nothing still costs the $0.02 run fee, about $0.44 a month for a quiet watch. Set a maximum cost per run on the task to cap the spend.

### FAQ

**Does it include Doffin or Hilma?** Not yet. Larger Norwegian and Finnish procurements are still found through TED. Direct Doffin and Hilma support needs free API keys and is planned.

**Why did I get nothing?** When a run returns no notices, the log and the `OUTPUT` record in the key-value store say why (for example "No new notices since last run"). Either there was nothing new, or your CPV codes and keywords were too narrow. Try a run with `onlyNew` set to `false` and empty filters to check. A run can also stop early if your maximum cost per run is lower than the run fee.

**Are award notices included?** Yes, if you set `noticeTypes` to `["awards"]` (or both). By default you get only calls for tenders, exactly as before. Awards are priced like tenders (the `notice` event). Winners that look like private individuals are never returned; see the personal data section.

**Can I search in Swedish, Danish, Finnish or Norwegian?** Mostly. Swedish, Finnish and Danish notices usually have their title in the buyer's language. Norwegian notices on TED are mostly in English, sometimes with a Norwegian heading after an English prefix, so use English keywords for Norway. Keywords match the short title and the buyer name as whole words, so add variants of compound words (e.g. `städ` and `städning`).

### More from Nordic Signals

- [Swedish Hiring Signals – Companies Hiring Now (Platsbanken)](https://apify.com/nordic_signals/swedish-hiring-signals): turns Swedish job ads from Platsbanken into one row per hiring company, with ad counts and growth trend. Useful for sales prospecting after you have found a tender.

# Actor input Schema

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

Buyer country. Empty = all four.

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

What to return. 'Tenders' = open calls for tenders (the default). 'Contract awards' = who won: adds winner, winner country and awarded value. Choose both to get both kinds.

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

CPV code or the start of one (digits only, 2 to 8, optional check digit), e.g. 72 for IT services or 71310000 for technical consultancy. Empty = all.

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

The notice must contain at least one of these as a whole word or phrase in its short title or the buyer's name (case-insensitive; 'it' does not match 'profiling'). Add word variants for compound words, e.g. 'städ' and 'städning'. Empty = all.

## `buyerNames` (type: `array`):

The buyer's name must contain at least one of these as a whole word or phrase (case-insensitive), e.g. 'Region Skåne' or 'Trafikverket'. Empty = all.

## `nutsCodes` (type: `array`):

NUTS code or the start of one, e.g. SE11 for Stockholm, SE22 for Skåne or NO072 for Troms. A notice matches if any of its places of performance starts with one of these. Notices without a stated place are left out when this is set. Empty = all.

## `minEstimatedValue` (type: `number`):

Only notices worth at least this much, in the notice's own currency (SEK, NOK, DKK or EUR; no conversion). Tenders are compared on estimated value, contract awards on awarded value. Notices with no stated value are left out when this is set.

## `onlyNew` (type: `boolean`):

Turn on for scheduled monitoring: returns only notices not delivered in earlier runs of the same watch (same countries, CPV codes and keywords). Off by default.

## `daysBack` (type: `integer`):

How many days of notices to return. With 'Only new' on, this applies only to the first run; later runs continue from the previous one.

## `minDaysToDeadline` (type: `integer`):

Tenders only: leave out calls that close in fewer than this many days from today, so there is still time to bid. Tenders without a stated deadline are left out when this is set. Contract awards are not affected.

## `maxResults` (type: `integer`):

Maximum notices per run (default 50). You pay $0.01 per notice delivered, so this also caps the cost of a run.

## Actor input object example

```json
{
  "countries": [
    "SE",
    "NO",
    "FI",
    "DK"
  ],
  "noticeTypes": [
    "tenders"
  ],
  "cpvCodes": [],
  "keywords": [],
  "buyerNames": [],
  "nutsCodes": [],
  "onlyNew": false,
  "daysBack": 3,
  "maxResults": 50
}
```

# 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 = {
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("nordic_signals/nordic-tender-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 = { "maxResults": 50 }

# Run the Actor and wait for it to finish
run = client.actor("nordic_signals/nordic-tender-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 '{
  "maxResults": 50
}' |
apify call nordic_signals/nordic-tender-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nordic_signals/nordic-tender-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/qY4Iqxu2eG1aUAB2x/builds/atMeZUfAb8jR66t5o/openapi.json
