# Nordic Tender Matcher (AI) (`fuze/nordic-tender-matcher`) Actor

AI agent that scores Norwegian, Finnish and Swedish public tenders against your company profile and tells you which ones to bid on. Actively maintained. · More no-code data tools → datatools-hub.vercel.app

- **URL**: https://apify.com/fuze/nordic-tender-matcher.md
- **Developed by:** [Fredrik Hansson](https://apify.com/fuze) (community)
- **Categories:** AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $132.00 / 1,000 tender matcheds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Nordic Tender Matcher (AI)

**Answers the question "which Nordic public tenders should my company bid on?" — not "here are 10,000 rows."**

Give it a short description of your company. It fetches the open public tenders from Norway, Finland and Sweden, uses OpenAI to pre-filter the obviously irrelevant ones, then scores each remaining tender against your profile and returns a ranked shortlist with a reason, an English summary and the key requirements for every match.

It's built for the moment a salesperson, bid manager or founder asks *"is there anything worth bidding on this week?"* — and for AI agents that need that answer as a tool call.

![Nordic Tender Matcher (AI) — input and sample output](https://datatools-hub.vercel.app/actor-shots/nordic-tender-matcher.png)

### Quick start

1. **Fill in the input** — set the fields in the input tab (prefilled with a working example).
2. **Click Start** — the Actor runs and writes one record per item to the dataset.
3. **Get your data** — export to **JSON, CSV or Excel**, or pull it via the API.

***

### How it works

1. **Fetch** — pulls open tenders from the selected sources (Norway = Doffin, Finland = Hilma, Sweden + current Finland = EU/TED), reusing the same official-API logic as the *Nordic Public Tenders* Actor. No scraping.
2. **Cheap pre-filter** — OpenAI turns your `companyProfile` into relevant CPV codes and keywords (one small call), and obviously irrelevant notices are dropped *before* any expensive analysis.
3. **AI scoring** — OpenAI scores each surviving tender and returns `matchScore` (0–100), `matchReason`, `summaryEn` and `keyRequirements`.
4. **Rank & deliver** — matches at or above `minScore` are sorted by score and returned, best first.

Real OpenAI token usage (and an estimated cost) is logged for every run and stored in the run's `SUMMARY`.

***

### Why it's different

A raw tender feed makes *you* read every notice in three languages and decide what fits. This Actor does the reading and the judgement call:

- **Ranked, not raw** — you see the best opportunities first, with a score.
- **Explained** — every match says *why* it fits your company (and where the gaps are).
- **English everywhere** — `summaryEn`, `matchReason` and `keyRequirements` are always English, regardless of the tender's language.
- **Agent-ready** — see "Use it as an AI-agent tool" below.

***

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `companyProfile` | `string` | — (required) | Free text: what you sell/do, size, regions, references. The more specific, the better the matching. |
| `countries` | `string[]` | all | Sources to search: `ted` (current SE + FI), `doffin` (NO), `hilma` (historical FI). |
| `publishedFrom` | `string` (ISO date) | 7 days ago | Only tenders published on/after this date. |
| `publishedTo` | `string` (ISO date) | today | Only tenders published on/before this date. |
| `minScore` | `integer` | `60` | Only deliver matches scored at or above this (0–100). |
| `maxResults` | `integer` | `25` | Max delivered matches (one `tender-matched` charge each). |

***

### Output

One dataset item per **match**, highest score first. It is the normalised tender schema plus the AI analysis:

| Field | Type | Description |
|---|---|---|
| `matchScore` | `number` | 0–100 fit score for *your* company. |
| `matchReason` | `string` | 2–3 sentences: why it fits (and gaps). |
| `summaryEn` | `string` | What's being bought, scope, key requirements — in English. |
| `keyRequirements` | `string[]` | The most important requirements/qualifications. |
| `id`, `source`, `country` | `string` | Stable id, source (`doffin`/`hilma`/`ted`), buyer country (`NO`/`FI`/`SE`). |
| `title`, `titleEn` | `string` | Original and English title. |
| `buyerName`, `buyerOrgNumber` | `string \| null` | Contracting authority and its org number. |
| `cpvCodes` | `string[]` | CPV classification codes. |
| `estimatedValue`, `currency` | `number \| null`, `string \| null` | Estimated value, if published. |
| `publishedDate`, `deadline` | `string \| null` | ISO-8601 dates. |
| `noticeType`, `procedureType` | `string \| null` | Notice and procedure type. |
| `description` | `string \| null` | Original-language description. |
| `url` | `string \| null` | Official public notice page. |

***

### Examples

#### 1. IT consultancy (Sweden + Finland)

**companyProfile**

```
Mid-sized Swedish IT consultancy (40 employees) specialising in cloud migration,
custom software and system integration for the public sector. Operates in Sweden
and southern Finland. References in regional healthcare and municipal e-services.
```

**Example output item**

```json
{
  "matchScore": 86,
  "country": "SE",
  "titleEn": "Sweden – IT consulting services – Framework agreement for system development",
  "buyerName": "Västra Götalandsregionen",
  "matchReason": "Strong fit: the buyer wants public-sector system development and integration, exactly your core offering, and your regional healthcare references are directly relevant. The required team size is within reach for a 40-person firm.",
  "summaryEn": "Framework agreement for custom system development and integration services for a regional authority. Multi-year, call-off based, with options for maintenance.",
  "keyRequirements": ["Public-sector references", "Agile delivery capability", "GDPR/security compliance", "Swedish-speaking team"],
  "deadline": "2026-11-03T23:59:00.000Z",
  "estimatedValue": 40000000,
  "currency": "SEK",
  "url": "https://ted.europa.eu/en/notice/-/detail/677811-2026"
}
```

#### 2. Facility cleaning company (Norway)

**companyProfile**

```
Norwegian facility-services company, 120 employees, providing commercial cleaning,
window cleaning and light grounds maintenance across Oslo and Viken. ISO 14001
certified. No construction work.
```

Returns high scores on municipal/regional cleaning and facility-maintenance framework agreements in Oslo/Viken; low scores on construction, catering or IT notices (dropped at pre-filter or scored below `minScore`).

#### 3. Medical equipment distributor (all three countries)

**companyProfile**

```
Distributor of medical imaging equipment and consumables (MRI, CT, ultrasound)
with service contracts. Sells to hospitals across the Nordics. Not a software vendor.
```

Returns imaging-equipment and service-contract tenders from hospitals/health regions in NO/FI/SE, with `keyRequirements` surfacing things like CE marking, service-level guarantees and installation scope — and clearly lower scores where a notice is only teleradiology *software* rather than hardware.

***

### Pricing — pay per event

Two events, so you only pay for work actually done:

| Event | When it's charged | Notes |
|---|---|---|
| `tender-scanned` | Per notice pre-filtered (the cheap scan step) | Covers fetching + cheap filtering. |
| `tender-matched` | Per match delivered after AI analysis | Covers the OpenAI scoring + margin. |

**How to price it (developer note).** The main cost is the OpenAI scoring of the tenders that survive pre-filtering. Set `tender-matched` high enough to cover the OpenAI tokens for the *analysed* set (including analysed-but-below-threshold notices) with margin, and keep `tender-scanned` low. Every run logs real token usage and an estimated AI cost (in the log and in `SUMMARY.ai`), so you can calibrate prices against actual spend. Note Apify enforces a **$0.10 minimum price per event**.

***

### Environment variables / secrets

| Variable | Required | Purpose |
|---|---|---|
| `OPENAI_API_KEY` | **Yes** | OpenAI API key — powers the pre-filter and scoring. |
| `OPENAI_MODEL` | No | Override the model (default `gpt-4o-mini`). |
| `DOFFIN_API_KEY` | For Norway (`doffin`) | Doffin Public API key (`Ocp-Apim-Subscription-Key`). |
| `HILMA_API_KEY` | For historical FI (`hilma`) | Hilma AVP-Read key (`Ocp-Apim-Subscription-Key`). |

Sweden + current Finland (via `ted`) need no key.

***

### Use it as an AI-agent tool (Apify MCP)

Once published, this Actor is callable through the **Apify MCP server** (`https://mcp.apify.com`). Any MCP client — Claude, Claude Code, Cursor, ChatGPT — can invoke it as a tool. Because the input is a single natural-language `companyProfile` and the output is a ranked, explained shortlist, an agent can be told:

> *"We're a Finnish security-services firm — find public tenders worth bidding on this month."*

…and call this Actor directly, then reason over the returned matches. The flat, English, scored output is designed to be consumed by an LLM without extra glue code.

***

### Run it locally

```bash
npm install
export OPENAI_API_KEY=...     # required
export DOFFIN_API_KEY=...        # for Norway (optional)
export HILMA_API_KEY=...         # for Finnish history (optional)

## Put your input in storage/key_value_stores/default/INPUT.json, then:
apify run
## or: npm run build && node dist/main.js
```

***

### Related

This Actor reuses the data-collection layer of **Nordic Public Tenders** (the raw-feed Actor). Use that one if you want the unfiltered notices; use this one if you want the answer to *"what should we bid on?"*.

### License

ISC.

### Related actors

- [Nordic Public Tenders (Norway, Finland, Sweden)](https://apify.com/fuze/nordic-public-tenders) — Collects public procurement notices from Norway (Doffin), Finland (Hilma) and Sweden
- [Nordic Company Registry (Norway, Finland)](https://apify.com/fuze/nordic-company-registry) — Official company-registry data from Norway (Brønnøysund) and Finland (PRH) in one
- [Signal Radar — AI Account Intelligence & ICP Scoring](https://apify.com/fuze/signal-radar) — Turn company domains into ICP-scored, signal-tagged account intelligence from public

# Actor input Schema

## `companyProfile` (type: `string`):

Free text: what your company sells/does, its size, the regions you operate in, and notable references. The more specific, the better the matching.

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

Which sources to search. Defaults to all.

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

ISO date (YYYY-MM-DD). Defaults to 7 days ago.

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

ISO date (YYYY-MM-DD). Defaults to today.

## `minScore` (type: `integer`):

Only deliver tenders scored at or above this value (0–100). Higher = fewer, stronger matches.

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

Maximum number of delivered matches. One 'tender-matched' charge per delivered match.

## Actor input object example

```json
{
  "companyProfile": "We are a mid-sized Swedish IT consultancy (40 employees) specialising in cloud migration, custom software and system integration for the public sector. We work across Sweden and southern Finland. References include regional healthcare and municipal e-service projects.",
  "countries": [
    "doffin",
    "hilma",
    "ted"
  ],
  "minScore": 60,
  "maxResults": 25
}
```

# Actor output Schema

## `matches` (type: `string`):

All delivered tender matches in the default dataset.

# 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 = {
    "companyProfile": "We are a mid-sized Swedish IT consultancy (40 employees) specialising in cloud migration, custom software and system integration for the public sector. We work across Sweden and southern Finland. References include regional healthcare and municipal e-service projects.",
    "countries": [
        "doffin",
        "hilma",
        "ted"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fuze/nordic-tender-matcher").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 = {
    "companyProfile": "We are a mid-sized Swedish IT consultancy (40 employees) specialising in cloud migration, custom software and system integration for the public sector. We work across Sweden and southern Finland. References include regional healthcare and municipal e-service projects.",
    "countries": [
        "doffin",
        "hilma",
        "ted",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("fuze/nordic-tender-matcher").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 '{
  "companyProfile": "We are a mid-sized Swedish IT consultancy (40 employees) specialising in cloud migration, custom software and system integration for the public sector. We work across Sweden and southern Finland. References include regional healthcare and municipal e-service projects.",
  "countries": [
    "doffin",
    "hilma",
    "ted"
  ]
}' |
apify call fuze/nordic-tender-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fuze/nordic-tender-matcher"
        }
    }
}
```

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/TeHRFfzHiUoSRzHUs/builds/FlGLPVlBfmTnX2DaI/openapi.json
