# Tender Opportunity Intelligence (`atlas-data/tender-opportunity-intelligence`) Actor

Normalize, deduplicate, rank, and track EU procurement opportunities from TED.

- **URL**: https://apify.com/atlas-data/tender-opportunity-intelligence.md
- **Developed by:** [Atlas](https://apify.com/atlas-data) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Tender Opportunity Intelligence

Turn the EU's Tenders Electronic Daily (TED) feed into a ranked, normalized opportunity stream your sales, bid, and market-intelligence workflows can use directly.

This Actor is more than an API relay. It traverses TED's unbounded `ITERATION` mode, normalizes multilingual and multi-lot fields, applies safe business filters, explains every relevance score, deduplicates notices, detects edits, and keeps each search profile's cursor and digest history isolated.

### Who it is for

Use it if you run EU public-sector pipeline discovery for a bid team, sales operation, specialist supplier, procurement consultancy, or market-intelligence product. It is especially useful when people currently search TED by hand, maintain brittle exports, or receive broad alerts that still require hours of triage.

The reason to pay is the workflow around the public data: bounded exhaustive traversal, stable normalized records, auditable ranking, change detection, and replay-safe incremental state. Those are the parts a team would otherwise have to build, operate, and monitor around the TED API before the feed is dependable enough for a CRM, warehouse, alerting flow, or analyst queue.

### What you get

Each dataset row uses output `schemaVersion` **1.0.0** and includes:

- stable TED notice ID, version, canonical URL, preferred-language title, and buyer details;
- buyer countries and place-of-performance countries as distinct fields, raw performance places, normalized CPV codes, and canonical calendar dates;
- every procedure/lot estimate as an amount-currency pair; a convenience largest value appears only when all estimates are safely comparable;
- notice type, procedure type/identifier, contract nature, and legal basis;
- a numeric relevance score plus the exact positive and negative signals that produced it;
- `changeType` (`snapshot`, `new`, `updated`, or `unchanged`) and top-level `changedFields`;
- `fetchedAt` and explicit TED attribution.

### Run modes

`full` emits every match with `changeType: "snapshot"`.

`incremental` emits only new and updated matches. It overlaps the previous completed publication watermark (three days by default) to catch edits to recently published notices.

`diff` performs the same durable tracking but also emits unchanged matches, which is useful for reconciliation and warehouse snapshots.

State is keyed by a SHA-256 fingerprint of all selection and relevance settings. Changing a country, keyword, date bound, value currency, language, or scoring profile cannot accidentally consume another configuration's cursor. Operational caps such as `maxItems` do not fork business state. Full-snapshot and change-tracking resumes are separate, so `full` never consumes or advances an incremental cursor.

### Example input

```json
{
  "mode": "incremental",
  "keywords": ["cybersecurity", "zero trust", "SOC"],
  "cpvPrefixes": ["72", "4873"],
  "buyerCountries": ["DEU", "NLD", "BEL"],
  "publicationFrom": "2026-01-01",
  "deadlineFrom": "2026-08-01",
  "minimumValue": 250000,
  "minimumValueCurrency": "EUR",
  "noticeTypes": ["cn-standard"],
  "language": "eng",
  "maxItems": 500,
  "relevance": {
    "include": [
      { "term": "managed service", "fields": ["title"], "weight": 25 },
      { "term": "72000000", "fields": ["cpv"], "weight": 12 }
    ],
    "exclude": [
      { "term": "hardware only", "fields": ["title"], "weight": 30 }
    ],
    "minimumScore": 8,
    "requireIncludeMatch": false
  }
}
````

Keywords use OR semantics across title and buyer name. CPV prefixes use OR semantics. Different filter groups combine with AND. Deadline bounds require at least one deadline satisfying each supplied bound. `minimumValue` and `minimumValueCurrency` must be supplied together; the filter succeeds only when an estimate in that exact currency reaches the threshold. No different/unknown currencies are compared and no FX conversion is attempted.

### Relevance model

The built-in explainable score adds 10 points per matched keyword and 8 per matched CPV prefix. `relevance.include` rules add their configured weight for each matching field; `exclude` rules subtract it. Set `minimumScore` to reject weak opportunities, and `requireIncludeMatch` when at least one positive profile rule must match.

Rules accept only these normalized fields: `title`, `buyer`, `cpv`, `country`, `place`, and `procedure`. `country` means buyer country; `place` covers performance-country codes and raw places. Matching is case-insensitive substring matching after Unicode NFC normalization. Every contribution appears in `relevance.signals`, so downstream users can audit or tune the ranking.

### Safety, traversal, and recovery

The Actor never inserts user text into TED expert-query syntax. The only server-side query is a generated publication-date window made from strictly validated, real-calendar `YYYY-MM-DD` values; keywords, codes, countries, values, deadlines, notice types, and ranking are client-side filters. TED currently returns publication dates with a numeric UTC offset and deadlines as timestamps. The Actor accepts only those documented forms, validates the calendar date, rejects arbitrary suffixes/impossible dates, and emits canonical `YYYY-MM-DD`.

TED's official API permits at most 250 notices per page and 10,000 requested fields per page. The Actor requests 20 fields, validates every envelope, row identity, count, iteration token, and timeout flag when present. The official OpenAPI does not require `timedOut`, so absence is accepted; `timedOut: true` still fails closed. Requests run sequentially at a conservative minimum 150 ms interval. HTTP 429, 5xx, and network failures receive at most four exponential-backoff retries.

`maxItems` counts matched opportunities—not raw rows—so a match behind a sparse backlog of rejected notices is still found. `maxPages` and `maxScannedItems` bound cost. When a cap lands inside a page, state records the exact query window, run mode, original page size, inbound token, row offset, and compact source ledger. Later runs finish that exact window even on the next day and preserve the saved page contract when caps or page size change. A change between `incremental` and `diff` invalidates the token and replays the saved window from its start; the ledger prevents already-handled rows from hiding an unprocessed row. Expired/invalid TED tokens use the same replay path.

The Actor writes dataset rows first, then the `OUTPUT` summary, and commits cursor/digest state **last**. A source, dataset, summary, lease, or state-write failure before that state commit therefore leaves work replayable. Replays can duplicate already-written dataset rows; consumers should upsert by `noticeId` plus `fetchedAt`/`changeType`. Conflicting rows with the same TED notice ID and version fail across pages and partial runs. Once a window completes, later content/version changes are handled normally as updates.

TED fetching and writes are sequential within each run. Cross-run state lives in the stable named key-value store `tender-opportunity-intelligence-state-v1`, not the run-exclusive default store. State commits are immutable, uniquely keyed snapshots. Their generation comes from the Request Queue service's lock-expiry timestamp. After acquiring the queue lock, a run loads every visible candidate and deterministically chooses the greatest document revision tuple: maximum configuration `updatedAt`, then snapshot generation, then sorted configuration revision content, with the full snapshot key used only when those semantic fields are identical. A stale snapshot therefore cannot win an equal-generation comparison solely because its random UUID sorts later. If the first snapshot listing is empty, the load retries it before reading the legacy `TENDER_OPPORTUNITY_STATE_V1` compatibility record; the first successful new commit migrates future visible reads to snapshots without overwriting that legacy record.

A single persistent request in the named Request Queue `tender-opportunity-intelligence-lock-v1` provides exclusive writer leases. This queue name is separate from the pre-existing state store and must be creatable on the first upgraded run. Each run acquires the request lock and prolongs it during traversal and around the final state write. Queue lock operations use the queue client's ownership key, so a stale release cannot unlock a later client. Release is best-effort after a successful durable commit: a release 5xx does not mark the committed run failed, and the lock remains unavailable until its server-side lease expires.

The Request Queue provides lock acquisition and lease-expiry semantics; separate dataset, `OUTPUT`, and state writes are not one transaction. A lock can expire during a slow external operation. The old writer's next ownership check fails, while immutable snapshots and document revision ordering prevent its delayed state from replacing the later owner's visible state. Already-written dataset rows or `OUTPUT` remain replayable. Keep schedules non-overlapping to avoid duplicate dataset rows and rejected overlapping runs.

Snapshot loads are bounded because the key-value store can be eventually consistent: if listed records disappear during reads, the Actor tries every listed candidate and refreshes the listing before selecting the newest document it actually loaded. Because the runner already holds the queue lock, no conforming writer can commit concurrently during this load. The remaining failure case is storage visibility lag: a durable snapshot omitted from every bounded listing cannot be selected, so the Actor can choose the newest visible snapshot or, after two empty listings, the legacy record. If listings advertise snapshots but none can be loaded during the bounded retries, the run fails closed instead of silently falling back.

Snapshot cleanup targets at most the newest 32 visible records and only deletes excess keys whose parsed generation is strictly less than the current writer's generation. It never deletes a same-generation, concurrent, or newer snapshot. Cleanup is best-effort, so failed cleanup, stale writers, or preservation of unsafe-to-delete generations can leave more than 32 records; the legacy compatibility record is not deleted.

### Bounded state

Each configuration retains compact overall/change-field digests for 180 days and at most 5,000 notices by default. Tune `stateRetentionDays` (7–730) and `maxStateEntries` (100–12,000). Up to eight recent configurations are normally retained; partial-resume configurations are protected even if that temporarily exceeds eight. Before every write, the entire UTF-8 JSON document is measured against a conservative 3,500,000-byte budget; completed old configurations and then oldest notice digests are pruned. Partial resume windows/source ledgers are never silently discarded. If protected resume data cannot fit, the run fails closed before writing the summary or state. An opportunity/configuration forgotten by retention is classified as `new` if encountered again.

### Run output

The `OUTPUT` key contains a summary with counts for scanned, matched, emitted, duplicates, and each change type; the stop reason; status (`succeeded` or `partial`); configuration hash; query window; dataset ID; timestamp; and attribution. A partial status is expected when an operational cap is reached and carries a safe resume cursor.

### Cost expectations

The committed Actor default is **1024 MB**. At that allocation, one hour of runtime consumes 1 compute unit (CU), so a 10-minute run consumes about 0.167 CU. At the **$0.20/CU observed on the release account**, that is about **$0.033 of compute for 10 minutes** or **$0.20 for one hour**, before storage operations and data transfer. A one-minute small run is roughly $0.0033 of compute.

These are planning examples, not a quoted price. The actual CU rate and storage/data-transfer charges depend on the runner's Apify plan, discount tier, pricing model, chosen memory override, and run duration. TED response volume and your `maxPages`, `maxScannedItems`, and `maxItems` limits determine how long a run takes. Start with a narrow date window and inspect the run's Usage details before scheduling a large recurring search.

### Local development

Requires Node.js 22.

```bash
npm ci
npm run lint
npm run typecheck
npm run build
npm test
npm run smoke:local
npm run smoke:live
npm audit --omit=dev
npm audit
apify validate-schema
```

`npm run smoke:live` makes a one-item anonymous request to the live TED API. Regular tests, named-store adapter tests, and the local smoke use fixtures and never need network access, cloud storage, or secrets.

The production image compiles TypeScript in an isolated build stage and installs runtime dependencies in the final Node.js 22 image. No API key is required for anonymous TED notice search.

### Data source and reuse

Source: [TED — Tenders Electronic Daily](https://ted.europa.eu/) and the anonymous [TED Search API](https://api.ted.europa.eu/v3/notices/search).

TED data may be reused commercially under the TED legal notice. TED editorial documentation is licensed under CC BY 4.0 and metadata is CC0. Preserve the attribution emitted with every row and review the current [TED legal notice](https://ted.europa.eu/en/legal-notice) for your use case. This Actor does not claim ownership of TED source data and does not provide legal or procurement advice.

# Actor input Schema

## `mode` (type: `string`):

full emits every match as snapshot; incremental emits only new/updated matches; diff also emits unchanged matches.

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

A notice matches when at least one keyword occurs in its normalized title or buyer name.

## `cpvPrefixes` (type: `array`):

Two to eight digits. Prefix matching lets 72 match the whole IT-services family.

## `buyerCountries` (type: `array`):

TED/ISO alpha-3 country codes, for example DEU, FRA, or CZE.

## `placeCountries` (type: `array`):

TED/ISO alpha-3 country codes, for example DEU, FRA, or CZE.

## `publicationFrom` (type: `string`):

Inclusive YYYY-MM-DD. Defaults to 30 days before publicationTo.

## `publicationTo` (type: `string`):

Inclusive YYYY-MM-DD. Defaults to today.

## `deadlineFrom` (type: `string`):

Keep notices with at least one deadline on or after this YYYY-MM-DD.

## `deadlineTo` (type: `string`):

Keep notices with at least one deadline on or before this YYYY-MM-DD.

## `minimumValue` (type: `number`):

Keep notices with at least one estimate in minimumValueCurrency that reaches this amount. Must be supplied together with minimumValueCurrency; no FX conversion is performed.

## `minimumValueCurrency` (type: `string`):

Three-letter currency code used for minimumValue comparisons, for example EUR. Must be supplied together with minimumValue.

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

TED notice type codes, for example cn-standard.

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

Three-letter TED language code. Falls back to English, then the first available translation.

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

Matched opportunities, not raw TED rows. Sparse searches continue behind rejected rows.

## `relevance` (type: `object`):

Explainable positive and negative term weights. Base keyword and CPV matches add 10 and 8 points.

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

Advanced. Official maximum is 250.

## `maxPages` (type: `integer`):

Stop after this many fully handled TED pages and save a safe resume cursor.

## `maxScannedItems` (type: `integer`):

Bound raw source rows independently of the matched-item limit.

## `overlapDays` (type: `integer`):

Rechecks recently published notices so later edits are detected.

## `stateRetentionDays` (type: `integer`):

Forget digests older than this many days after a successful run.

## `maxStateEntries` (type: `integer`):

Keep state bounded by retaining only the most recently seen notice digests.

## Actor input object example

```json
{
  "mode": "incremental",
  "keywords": [],
  "cpvPrefixes": [],
  "buyerCountries": [],
  "placeCountries": [],
  "noticeTypes": [],
  "language": "eng",
  "maxItems": 1000,
  "relevance": {
    "include": [],
    "exclude": [],
    "minimumScore": 0,
    "requireIncludeMatch": false
  },
  "pageSize": 250,
  "maxPages": 100,
  "maxScannedItems": 10000,
  "overlapDays": 3,
  "stateRetentionDays": 180,
  "maxStateEntries": 5000
}
```

# Actor output Schema

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

Normalized, ranked, and change-classified opportunity rows.

## `summary` (type: `string`):

Counts, stop reason, query window, configuration hash, and attribution.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("atlas-data/tender-opportunity-intelligence").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("atlas-data/tender-opportunity-intelligence").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call atlas-data/tender-opportunity-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=atlas-data/tender-opportunity-intelligence",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Tender Opportunity Intelligence",
        "description": "Normalize, deduplicate, rank, and track EU procurement opportunities from TED.",
        "version": "1.0",
        "x-build-id": "GqYE8Ym9XMxKcov9G"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/atlas-data~tender-opportunity-intelligence/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-atlas-data-tender-opportunity-intelligence",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/atlas-data~tender-opportunity-intelligence/runs": {
            "post": {
                "operationId": "runs-sync-atlas-data-tender-opportunity-intelligence",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/atlas-data~tender-opportunity-intelligence/run-sync": {
            "post": {
                "operationId": "run-sync-atlas-data-tender-opportunity-intelligence",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "mode": {
                        "title": "Run mode",
                        "enum": [
                            "full",
                            "incremental",
                            "diff"
                        ],
                        "type": "string",
                        "description": "full emits every match as snapshot; incremental emits only new/updated matches; diff also emits unchanged matches.",
                        "default": "incremental"
                    },
                    "keywords": {
                        "title": "Keywords",
                        "maxItems": 50,
                        "type": "array",
                        "description": "A notice matches when at least one keyword occurs in its normalized title or buyer name.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 120
                        },
                        "default": []
                    },
                    "cpvPrefixes": {
                        "title": "CPV prefixes",
                        "maxItems": 100,
                        "type": "array",
                        "description": "Two to eight digits. Prefix matching lets 72 match the whole IT-services family.",
                        "items": {
                            "type": "string",
                            "pattern": "^[0-9]{2,8}$"
                        },
                        "default": []
                    },
                    "buyerCountries": {
                        "title": "Buyer countries",
                        "maxItems": 50,
                        "type": "array",
                        "description": "TED/ISO alpha-3 country codes, for example DEU, FRA, or CZE.",
                        "items": {
                            "type": "string",
                            "pattern": "^[A-Za-z]{3}$"
                        },
                        "default": []
                    },
                    "placeCountries": {
                        "title": "Place-of-performance countries",
                        "maxItems": 50,
                        "type": "array",
                        "description": "TED/ISO alpha-3 country codes, for example DEU, FRA, or CZE.",
                        "items": {
                            "type": "string",
                            "pattern": "^[A-Za-z]{3}$"
                        },
                        "default": []
                    },
                    "publicationFrom": {
                        "title": "Published from",
                        "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
                        "type": "string",
                        "description": "Inclusive YYYY-MM-DD. Defaults to 30 days before publicationTo."
                    },
                    "publicationTo": {
                        "title": "Published through",
                        "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
                        "type": "string",
                        "description": "Inclusive YYYY-MM-DD. Defaults to today."
                    },
                    "deadlineFrom": {
                        "title": "Deadline from",
                        "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
                        "type": "string",
                        "description": "Keep notices with at least one deadline on or after this YYYY-MM-DD."
                    },
                    "deadlineTo": {
                        "title": "Deadline through",
                        "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
                        "type": "string",
                        "description": "Keep notices with at least one deadline on or before this YYYY-MM-DD."
                    },
                    "minimumValue": {
                        "title": "Minimum estimated value",
                        "minimum": 0,
                        "type": "number",
                        "description": "Keep notices with at least one estimate in minimumValueCurrency that reaches this amount. Must be supplied together with minimumValueCurrency; no FX conversion is performed."
                    },
                    "minimumValueCurrency": {
                        "title": "Minimum value currency",
                        "pattern": "^[A-Za-z]{3}$",
                        "type": "string",
                        "description": "Three-letter currency code used for minimumValue comparisons, for example EUR. Must be supplied together with minimumValue."
                    },
                    "noticeTypes": {
                        "title": "Notice types",
                        "maxItems": 50,
                        "type": "array",
                        "description": "TED notice type codes, for example cn-standard.",
                        "items": {
                            "type": "string",
                            "pattern": "^[A-Za-z0-9][A-Za-z0-9-]{0,79}$"
                        },
                        "default": []
                    },
                    "language": {
                        "title": "Preferred language",
                        "pattern": "^[a-z]{3}$",
                        "type": "string",
                        "description": "Three-letter TED language code. Falls back to English, then the first available translation.",
                        "default": "eng"
                    },
                    "maxItems": {
                        "title": "Maximum matched items",
                        "minimum": 1,
                        "maximum": 100000,
                        "type": "integer",
                        "description": "Matched opportunities, not raw TED rows. Sparse searches continue behind rejected rows.",
                        "default": 1000
                    },
                    "relevance": {
                        "title": "Weighted relevance profile",
                        "type": "object",
                        "description": "Explainable positive and negative term weights. Base keyword and CPV matches add 10 and 8 points.",
                        "properties": {
                            "include": {
                                "title": "Positive rules",
                                "description": "Terms that add to an opportunity's score.",
                                "type": "array",
                                "maxItems": 100,
                                "items": {
                                    "type": "object",
                                    "additionalProperties": false,
                                    "required": [
                                        "term",
                                        "fields",
                                        "weight"
                                    ],
                                    "properties": {
                                        "term": {
                                            "title": "Term",
                                            "description": "Case-insensitive text or code fragment to match.",
                                            "type": "string",
                                            "minLength": 1,
                                            "maxLength": 120
                                        },
                                        "fields": {
                                            "title": "Fields",
                                            "description": "Normalized fields in which this term should be matched.",
                                            "type": "array",
                                            "editor": "select",
                                            "minItems": 1,
                                            "uniqueItems": true,
                                            "items": {
                                                "type": "string",
                                                "enum": [
                                                    "title",
                                                    "buyer",
                                                    "cpv",
                                                    "country",
                                                    "place",
                                                    "procedure"
                                                ],
                                                "enumTitles": [
                                                    "Title",
                                                    "Buyer",
                                                    "CPV",
                                                    "Buyer country",
                                                    "Performance place",
                                                    "Procedure"
                                                ]
                                            }
                                        },
                                        "weight": {
                                            "title": "Weight",
                                            "description": "Positive score contribution for a match.",
                                            "type": "number",
                                            "minimum": 0.1,
                                            "maximum": 100
                                        }
                                    }
                                },
                                "default": []
                            },
                            "exclude": {
                                "title": "Negative rules",
                                "description": "Terms that subtract from an opportunity's score.",
                                "type": "array",
                                "maxItems": 100,
                                "items": {
                                    "type": "object",
                                    "additionalProperties": false,
                                    "required": [
                                        "term",
                                        "fields",
                                        "weight"
                                    ],
                                    "properties": {
                                        "term": {
                                            "title": "Term",
                                            "description": "Case-insensitive text or code fragment to match.",
                                            "type": "string",
                                            "minLength": 1,
                                            "maxLength": 120
                                        },
                                        "fields": {
                                            "title": "Fields",
                                            "description": "Normalized fields in which this term should be matched.",
                                            "type": "array",
                                            "editor": "select",
                                            "minItems": 1,
                                            "uniqueItems": true,
                                            "items": {
                                                "type": "string",
                                                "enum": [
                                                    "title",
                                                    "buyer",
                                                    "cpv",
                                                    "country",
                                                    "place",
                                                    "procedure"
                                                ],
                                                "enumTitles": [
                                                    "Title",
                                                    "Buyer",
                                                    "CPV",
                                                    "Buyer country",
                                                    "Performance place",
                                                    "Procedure"
                                                ]
                                            }
                                        },
                                        "weight": {
                                            "title": "Weight",
                                            "description": "Score penalty for a match.",
                                            "type": "number",
                                            "minimum": 0.1,
                                            "maximum": 100
                                        }
                                    }
                                },
                                "default": []
                            },
                            "minimumScore": {
                                "title": "Minimum score",
                                "description": "Reject opportunities whose final score is below this value.",
                                "type": "number",
                                "minimum": -10000,
                                "maximum": 10000,
                                "default": 0
                            },
                            "requireIncludeMatch": {
                                "title": "Require a positive profile match",
                                "description": "Require at least one include rule to match.",
                                "type": "boolean",
                                "default": false
                            }
                        },
                        "default": {
                            "include": [],
                            "exclude": [],
                            "minimumScore": 0,
                            "requireIncludeMatch": false
                        },
                        "additionalProperties": false
                    },
                    "pageSize": {
                        "title": "TED page size",
                        "minimum": 1,
                        "maximum": 250,
                        "type": "integer",
                        "description": "Advanced. Official maximum is 250.",
                        "default": 250
                    },
                    "maxPages": {
                        "title": "Maximum pages per run",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Stop after this many fully handled TED pages and save a safe resume cursor.",
                        "default": 100
                    },
                    "maxScannedItems": {
                        "title": "Maximum TED rows scanned",
                        "minimum": 1,
                        "maximum": 25000,
                        "type": "integer",
                        "description": "Bound raw source rows independently of the matched-item limit.",
                        "default": 10000
                    },
                    "overlapDays": {
                        "title": "Incremental overlap days",
                        "minimum": 0,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Rechecks recently published notices so later edits are detected.",
                        "default": 3
                    },
                    "stateRetentionDays": {
                        "title": "Digest retention days",
                        "minimum": 7,
                        "maximum": 730,
                        "type": "integer",
                        "description": "Forget digests older than this many days after a successful run.",
                        "default": 180
                    },
                    "maxStateEntries": {
                        "title": "Maximum digests per configuration",
                        "minimum": 100,
                        "maximum": 12000,
                        "type": "integer",
                        "description": "Keep state bounded by retaining only the most recently seen notice digests.",
                        "default": 5000
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
