datagrit

datagrit › Guides › TED Contract Expiry Radar - Recompete Leads

Guide

How to find EU public contracts that expire in the next 12 months

Pull expiring EU public contracts from TED award notices with Python: incumbent, buyer, value, end date and CPV code, ready for a bid pipeline.

Published 2026-10-01 · uses the TED Contract Expiry Radar - Recompete Leads Actor

Public buyers in Europe re-tender service contracts every few years. Cleaning, IT support, ticketing software, waste collection: the contract runs its term, then the authority publishes a new call for tenders and the incumbent defends its position.

The supplier that wins a recompete is rarely the one that reads the new tender on publication day. It is the one that knew a year earlier which contracts were ending and talked to the buyer in time. Bid managers, public-sector sales teams and consultancies that track competitors all need the same list: which contracts in my sector end in the next 6 to 18 months, who holds them and who buys them.

The data to build that list is public. Getting it into a spreadsheet is the hard part.

Where the data comes from

Buyers in the EU member states and several other European countries publish contract award notices on TED (Tenders Electronic Daily), the EU's official tender portal, which has a public search API at api.ted.europa.eu/v3/notices/search. An award notice names the buyer, the winning supplier, the awarded value, the CPV classification and usually how long the contract runs.

In practice, building an expiry list from that API runs into several problems:

The TED Contract Expiry Radar Actor on Apify handles these. It reads the window in monthly slices, splits any slice above 15,000 notices into shorter date windows, paces requests at about one per second, and matches every per-lot field to its lot by the lot identifier. A list is only used when it has exactly one entry per lot; otherwise the field stays empty rather than guessed. When an end date is missing, it is computed from the lot start date or from an unambiguous conclusion date plus the published duration, and every row says which method was used.

Step by step

1. Define the input

All fields are optional. A realistic input for an IT services company looking at Germany and France, skipping contracts that end in under three months:


{
  "queries": ["software"],
  "countries": ["DE", "FR"],
  "cpvPrefixes": ["72"],
  "incumbents": [],
  "expiresAfterMonths": 3,
  "expiresBeforeMonths": 15,
  "minValue": 100000,
  "includeFrameworkAgreements": true,
  "estimateMissingEndDates": true,
  "maxItems": 200
}

Details:

2. Run it from Python


from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run_input = {
    "queries": ["software"],
    "countries": ["DE", "FR"],
    "cpvPrefixes": ["72"],
    "expiresAfterMonths": 3,
    "expiresBeforeMonths": 15,
    "minValue": 100000,
    "maxItems": 200,
}

run = client.actor("datagrit/ted-contract-expiry-radar").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("found"):
        print(item["contractEnd"], item["buyerName"], item["incumbentName"], item["awardValue"])

3. Or with cURL


curl -X POST \
  "https://api.apify.com/v2/acts/datagrit~ted-contract-expiry-radar/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries": ["software"], "expiresBeforeMonths": 6, "maxItems": 50}'

The synchronous endpoint suits small, filtered runs. An unfiltered 12-month window matched about 251,000 notices on 30 September 2026 and took roughly 45 minutes, so start large runs asynchronously, as in the Python example. Results come soonest-expiry first, so a maxItems cap returns the most urgent contracts and stops early.

What you get

Each row is one awarded lot. The most useful fields:

FieldExampleMeaning
contractEnd2026-12-31End date, stated or computed
endDateSourcestart+durationstated, start+duration or award+duration
daysToExpiry92Days from the run date to contractEnd
buyerNameTallinna TranspordiametAuthority that will buy again
buyerCountryESTThree-letter ISO code
incumbentNameAS Connecto EestiCurrent supplier of this lot
awardValue / currency480000 / EURAwarded value of the lot
contractLengthMonths36Published duration
renewalOptionsMax1Renewals the buyer reserved
cpvCode72000000Main CPV code of the lot

Rows also carry sourceUrl, frameworkAgreement, renewalTerms, lotTitle, lotDescription and allIncumbents (every winner of the notice, when a lot's winner cannot be determined).

Recipes

Weekly recompete list in a spreadsheet

Schedule the Actor weekly on Apify with a 6–18 month window and your CPV prefixes, then pull the dataset as CSV into Google Sheets or your CRM. Use id (notice number plus lot) as the unique key, so each lot becomes one record you can assign to an account owner and update week after week.

Competitor watch

Put competitor names into incumbents and widen the window to 24 months. You get a calendar of when each competitor's public contracts end, which buyers they depend on, and which contracts carry renewal options.

Market sizing in pandas


import pandas as pd

df = pd.DataFrame([i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if i.get("found")])

eur = df[df["currency"] == "EUR"]  # values are not converted between currencies
summary = (
    eur.groupby(["buyerCountry", "cpvCode"])
       .agg(lots=("id", "count"), value_eur=("awardValue", "sum"), median_months=("contractLengthMonths", "median"))
       .sort_values("value_eur", ascending=False)
)
print(summary.head(20))

# Share of rows whose end date was computed rather than stated
print(df["endDateSource"].value_counts(normalize=True))

Limitations

The data is what the EU publishes openly on TED; the Actor does not log in or bypass access controls.

Wrapping up

If you already know your sectors and countries, a scheduled run turns TED into a recompete pipeline. Pricing is pay per result; the Actor page is on Apify and the field reference lives in the documentation.

Try TED Contract Expiry Radar - Recompete Leads on ApifyActor documentation

More guides