TED EU Tender Monitor — Search & Delta-Track Tenders avatar

TED EU Tender Monitor — Search & Delta-Track Tenders

Pricing

from $0.01 / 1,000 results

Go to Apify Store
TED EU Tender Monitor — Search & Delta-Track Tenders

TED EU Tender Monitor — Search & Delta-Track Tenders

Search EU public procurement notices (TED) by keyword, CPV code, country and date as clean JSON, with a delta mode returning only new tenders per run. Built for logistics/freight bid teams. No API key needed.

Pricing

from $0.01 / 1,000 results

Rating

0.0

(0)

Developer

Daniel Posztos

Daniel Posztos

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

7 days ago

Last modified

Categories

Share

TED EU Tender Monitor — search & delta-track EU public tenders as JSON

SEO title: TED EU Tender Monitor — Search Public Tenders as JSON SEO description: Search EU public tenders (TED) by keyword, CPV code, country and date as clean JSON, with a delta mode returning only new tenders per run. Built for logistics/freight bid teams. No API key needed.

Search TED (Tenders Electronic Daily) — the official EU public procurement portal — by keyword, CPV code, buyer country, and publication/deadline date, and get clean, structured JSON back instead of TED's own XML/eForms notice format. Add a scheduled run with mode: "delta" and get only the tenders you haven't seen before, so you can watch a saved search for new opportunities without re-processing everything every time.

No scraping, no login, no API key: TED's Search API v3 is public and unauthenticated.

Why this exists

TED's own Search API is real and free, but not turnkey:

  • Its query language ("expert query") is a small custom DSL with undocumented gotchas (see "TED API discoveries" below) — dates in YYYYMMDD, not ISO-8601; countries in ISO alpha-3, not alpha-2; response field types that don't always match what TED's own OpenAPI spec declares.
  • Responses are nested, multi-language objects (a title is {"eng": "...", "deu": "...", ...} for all 24 EU languages) rather than a flat row.
  • There's no "give me only what's new since last time" mode — every scheduled run gets everything again, and building your own dedup layer is exactly the kind of undifferentiated work this actor exists to remove.

This actor wraps all of that: one flat JSON object per tender, one input schema, and a delta mode with its own persistent "already seen" tracking.

Input

FieldTypeDefaultDescription
keywordsarray of stringsFull-text search terms, OR'ed together (any match). E.g. ["logistics", "freight forwarding"]. Cannot contain " or \.
cpv_codesarray of stringsCPV codes, 2/4/6/8 digits (chapter/group/class/full), e.g. "60" (transport services), "6024" (freight transport by road), "63120000" (storage and warehousing). The official -N check-digit suffix is accepted and ignored.
countriesarray of strings["DE", "HU"]ISO 3166-1 alpha-2 buyer-country codes, e.g. "DE", "HU". This is the contracting authority's country, not the notice's publication language.
published_afterstring ("YYYY-MM-DD")"2026-01-01"Only notices published on/after this date.
deadline_beforestring ("YYYY-MM-DD")Only notices whose earliest tender-submission deadline is on/before this date. Omit to not filter by deadline (also returns notice types with no deadline at all, e.g. contract award notices).
modestring"full""full": every matching tender every run. "delta": only tenders not already returned by a previous run with the exact same keywords/cpv_codes/countries/date filters. See "Delta mode" below.
max_itemsinteger20Max tenders to return (and charge for) per run, 1–500.

At least one of keywords, cpv_codes, or countries is required — published_after/deadline_before narrow a search, they don't define one on their own.

Why published_after defaults to a fixed "2026-01-01" instead of a relative "N days ago": a static JSON Schema default can't compute "today minus 90 days" — it can only be a fixed value. A fixed, sufficiently-recent-when-written date keeps matching plenty of notices for a long time after this actor was built (TED publishes hundreds of notices a day), which is what actually matters for a default Run/daily health-check to keep succeeding — not that the date stays "recent" forever. If this actor is still running years from now and the default Run starts returning few/no results, that default is the thing to bump, not a sign of a bug.

Worked example — input

{
"keywords": ["logistics", "freight forwarding", "warehousing"],
"countries": ["DE", "HU"],
"published_after": "2026-01-01",
"mode": "full",
"max_items": 20
}

Worked example — output (real data, 2 of 20 items, fetched 2026-07-06)

[
{
"tender_id": "464044-2026",
"title": "Germany – Storage and warehousing services – Logistikdienstleistung Hilfsmittelversorgung",
"buyer": "BG Klinikum Hamburg gGmbH",
"country": ["DE"],
"cpv": ["63120000", "64110000", "33141000", "33140000"],
"deadline": "2026-07-13",
"value_estimate": null,
"currency": null,
"url": "https://ted.europa.eu/en/notice/464044-2026/html",
"publication_date": "2026-07-06",
"notice_type": "cn-standard"
},
{
"tender_id": "442473-2026",
"title": "Germany, Belgium – Water transport services – On-/offshore Cable Jointing Services & Offshore Cable Repair Logistic Services",
"buyer": "50Hertz Transmission GmbH: Vertragsschließende Einheit / Auftraggeber (Contracting Entity) für Lots 1–5",
"country": ["DE", "BE"],
"cpv": ["60600000", "45314300", "50532400"],
"deadline": null,
"value_estimate": 110000000.0,
"currency": "EUR",
"url": "https://ted.europa.eu/en/notice/442473-2026/html",
"publication_date": "2026-06-29",
"notice_type": "cn-standard"
}
]

value_estimate/deadline are null on plenty of real notices — TED doesn't require every field for every notice type (e.g. a contract award notice, notice_type: "can-standard", has no submission deadline; not every buyer discloses an estimated value). This is expected, not a bug — see "Known limitations".

Output schema

FieldTypeNotes
tender_idstringTED's own publication number, e.g. "461933-2026". Globally unique; also the delta-mode dedup key.
titlestring or nullNotice title (English; TED auto-translates into all 24 EU languages, so this is present for effectively every notice).
buyerstring or nullContracting authority name.
countryarray of stringsBuyer country/ies, ISO 3166-1 alpha-2. A list because joint-procurement notices can have buyers from multiple countries.
cpvarray of stringsCPV codes (main + additional), deduplicated.
deadlinestring or nullEarliest tender-submission deadline across the notice's lots, "YYYY-MM-DD".
value_estimatenumber or nullEstimated value of the notice's first lot only — see "Known limitations".
currencystring or nullISO 4217 currency for value_estimate.
urlstringLink to the full notice on ted.europa.eu (English UI).
publication_datestring or null"YYYY-MM-DD".
notice_typestring or nullTED's own code, e.g. "cn-standard" (contract notice), "can-standard" (contract award notice), "pin-standard" (prior information notice).

Delta mode

Set "mode": "delta" on a scheduled run to get only tenders not already returned by a previous run with the exact same keywords/cpv_codes/countries/published_after/deadline_before (changing max_items or mode itself doesn't count as a different search — only the filter fields do).

How it works: a named Key-Value store (ted-tender-monitor-seen) holds one "seen tender IDs" record per distinct filter combination, keyed by a stable hash of those filter fields. Each delta run loads that record, skips any matching tender already in it, charges/delivers only the rest, and then adds the newly-delivered tenders' IDs back into the record. A tender dropped by a budget stop (ACTOR_MAX_TOTAL_CHARGE_USD) is never marked "seen" — it was never paid for or delivered, so it's offered again on the next run. The seen-set is bounded (pruned past 400 days old relative to its own newest entry, and hard-capped at 50,000 IDs) so it can't grow forever across months of scheduled runs.

0 new tenders in delta mode is a normal, expected result — most days, nothing new matches a narrow saved search. This actor logs an explicit INFO line ("Delta mode: 0 new tenders since last run (...)") and sets that as the run's status message, rather than the WARNING that full mode uses for its own "valid search, genuinely 0 matches" case (see "Error messages"). full mode runs never read or write the delta store at all, so alternating full and delta runs on the same search can't corrupt the delta baseline.

Verified locally: running the same delta-mode input twice in a row returned 15 real tenders on the first run and exactly 0 on the second, with the INFO-not-WARNING message, and the Key-Value store persisting the 15 tender IDs between runs.

Pricing (pay-per-event)

EventPriceWhen it's charged
tender-result$0.005Once per returned tender. In delta mode, only tenders not already returned by a previous run with the same search are charged.

Plus Apify's own apify-actor-start synthetic event (first 5 seconds of compute free, platform-managed).

Examples: full mode, 20 matching tenders → 20 × $0.005 = $0.10. delta mode on a daily schedule with typically 2–3 genuinely new tenders/day → ~$0.01–0.015/day; a quiet day with 0 new tenders costs $0.00.

If a run's cost would exceed the Max total charge USD you set for it, the actor stops producing further tenders at exactly that point — every delivered tender was paid for, and nothing paid-for is ever dropped. Verified locally (ACTOR_TEST_PAY_PER_EVENT=1): $3 budget → 1 tender charged = 1 delivered; $5 → 2 = 2; both stopped gracefully with exit code 0, no crash.

Error messages

  • No input at all / no search criteria: explains the minimal valid input — at least one of keywords/cpv_codes/countries.
  • Dates alone, no other criteria: "'published_after'/'deadline_before' alone are not enough -- they narrow a search, they don't define one."
  • Unrecognized country code: "'countries' entry 'ZZ' is not a recognized ISO 3166-1 alpha-2 country code..." — also rejects alpha-3 codes (e.g. "DEU") since this input field is alpha-2 by convention with the rest of this portfolio.
  • Malformed CPV code: explains the required 2/4/6/8-digit shape with worked examples.
  • Keyword containing " or \: rejected with a suggested fix (these characters can't appear in a TED full-text search term as constructed by this actor).
  • Malformed date / published_after after deadline_before / unknown mode / max_items out of range: each rejected with the exact rule and an example fix.
  • 0 tenders with otherwise valid input, full mode: the run still succeeds (exit 0), but the log carries an explicit WARNING: 0 results produced despite valid input (...) line and the run's status message says so — never a silent "nothing happened."
  • 0 tenders in delta mode: explicit INFO, not WARNING — this is the expected steady state, not a problem. See "Delta mode".
  • TED API unreachable: logged as an ERROR with the underlying cause; the run still completes (0 tenders, same as above) rather than crashing outright.

TED API discoveries (undocumented outside this actor's code comments)

Recorded here because there is no user-facing grammar/field reference for TED's Search API v3 beyond its interactive Expert Search page — these were found by hitting the API directly and reading its own error messages (2026-07-06):

  • No API key needed. POST https://api.ted.europa.eu/v3/notices/search works fully unauthenticated; no rate-limit headers were observed.
  • POST only — a GET on the same path returns HTTP 405. The full (otherwise unlinked) OpenAPI spec is at https://api.ted.europa.eu/api-v3.yaml.
  • Expert-query dates are YYYYMMDD, not ISO-8601 (confirmed via the server's own format-error message).
  • buyer-country takes ISO 3166-1 alpha-3 ("DEU", not "DE") — this actor's countries input stays alpha-2 for consistency with the rest of the portfolio and converts internally (src/countries.py).
  • estimated-value-lot is declared as number in TED's own OpenAPI spec but is actually returned as a numeric string (e.g. "110000000") — this actor coerces it to a float. Its companion array estimated-value-cur-lot isn't even guaranteed to be the same length (observed: 6 values, 1 shared currency).
  • Notice titles are auto-translated into all 24 EU languages regardless of the notice's original language — the English version was present on every notice fetched during development, including ones from non-EU buyers (e.g. a Kazakhstan buyer).

Known limitations (documented, not hidden)

  • value_estimate/currency reflect the first lot only. A multi-lot tender's lots can have different estimated values and even different currencies — summing them would silently mix currencies or misrepresent the tender. The full per-lot breakdown is always one click away at url.
  • deadline is the earliest across all lots, not necessarily every lot's deadline (most multi-lot tenders share one deadline anyway).
  • Delta mode's "seen" tracking is per exact filter combination. Changing keywords/cpv_codes/countries/dates starts a fresh dedup baseline for that new combination (by design — it's a different saved search) rather than trying to guess overlap with a previous one.
  • max_items is capped at 500 per run — TED's own Search API supports far more via pagination (up to 15,000 retrievable notices per query in the default pagination mode), but this actor stays well under that ceiling to keep runs fast and costs predictable; there's no scroll/iteration-mode support (not needed at this ceiling).

Data source (public, no scraping)

TED (Tenders Electronic Daily) Search API v3, the European Union's official public procurement notice portal: https://api.ted.europa.eu/v3/notices/search. Public, unauthenticated, official EU data — the same API TED's own Expert Search page uses.

Example use cases (logistics & freight, this actor's niche)

  • Freight forwarders / 3PLs: watch for new road/rail/sea freight tenders (cpv_codes: ["60", "63"]) across the countries you operate in, on a daily delta schedule.
  • Warehousing operators: keywords: ["warehousing", "storage and distribution"] to catch contract notices for third-party logistics/fulfillment contracts.
  • Bid/tender teams at logistics companies: combine countries (your target markets) with deadline_before to get a rolling shortlist of tenders still open for bidding, sorted newest-first.
  • Consultancies tracking a client's procurement pipeline: a full-mode run against a client's buyer-country + relevant CPV codes as a standing research query.