Developer guides

How to find public contracts that end soon: re-tender data from TED and Find a Tender

No procurement portal publishes a list of contracts that are about to expire; the end date has to be derived from the award notice (start plus duration, or a stated end date) and each derivation can be wrong in a known way. Source: Arcadium Data, data as of .

Suppliers want the contract that was awarded two years ago, because the buyer must tender it again. The buyer wants the same: a pipeline of upcoming procurements. Everything needed is public, but it is scattered across award notices, and the one date you need is often missing. Competitors and incumbents already watch this: it is the basis of "re-tender radar" products from bid-intelligence firms.

This guide builds the list from the source notices, explains where the estimate fails, and then shows the API that does it for TED and Find a Tender.

The official route: derive it from award notices

Where the dates live

An award notice (TED: can-standard and relatives; UK: contract details UK7 and the OCDS contracts[] block) states some of these, per lot:

Use them in this order: a stated end date; else start plus duration; else conclusion date plus duration; and only for award notices, as a last resort, the publication day plus duration. Write down which one you used.

curl

# TED: result notices of one day with the period fields (anonymous, no key)
curl -s https://api.ted.europa.eu/v3/notices/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"publication-date=20261023 AND notice-type IN (can-standard can-social can-desg can-tran veat can-modif compl)","fields":["publication-number","notice-type","buyer-name","winner-name","contract-duration-start-date-lot","contract-duration-end-date-lot","duration-period-value-lot","duration-period-unit-lot","contract-conclusion-date","renewal-maximum-lot","framework-agreement-lot"],"limit":250,"page":1,"paginationMode":"PAGE_NUMBER"}'

Python

from datetime import date
from dateutil.relativedelta import relativedelta  # pip install python-dateutil

def contract_end(n: dict):
    """n: a normalised notice with start, end, concluded (date or None), duration_months (int or None).
    Returns (end_date, basis) or (None, None)."""
    if n.get("end"):
        return n["end"], "end_date"
    if n.get("duration_months"):
        if n.get("start"):
            return n["start"] + relativedelta(months=n["duration_months"]), "start_plus_duration"
        if n.get("concluded"):
            return n["concluded"] + relativedelta(months=n["duration_months"]), "concluded_plus_duration"
    return None, None

def retender_window(end: date, framework: bool, months: int):
    """Typical lead time: a buyer starts the follow-up 9 months before the end, 12 for frameworks and 3+ year contracts."""
    lead = 12 if framework or months >= 36 else 9
    return end - relativedelta(months=lead), end - relativedelta(months=2)

TypeScript

export type Period = { start?: Date; end?: Date; concluded?: Date; durationMonths?: number };

const addMonths = (d: Date, m: number): Date => {
  const r = new Date(d);
  r.setUTCMonth(r.getUTCMonth() + m);
  return r;
};

export function contractEnd(p: Period): { end: Date; basis: string } | null {
  if (p.end) return { end: p.end, basis: 'end_date' };
  if (!p.durationMonths) return null;
  if (p.start) return { end: addMonths(p.start, p.durationMonths), basis: 'start_plus_duration' };
  if (p.concluded) return { end: addMonths(p.concluded, p.durationMonths), basis: 'concluded_plus_duration' };
  return null;
}

Where UK data helps

Under the Procurement Act 2023, buyers publish more than award notices. A pipeline notice (UK1) and a planned procurement notice (UK3) list contracts the buyer will tender, months before the call. Contract details (UK7) state the contract period. A contract change (UK10) can move the end date; a contract termination (UK11) ends one early. The notice types guide explains all seventeen codes, and the Find a Tender API guide shows how to read them.

From a list to leads

A raw expiry list is long and mostly irrelevant. Narrow it in this order: country and CPV division you can serve, then a value band that fits your delivery size, then contracts whose end date came from a stated end date rather than an estimate, then the buyers you have won from or lost to before. For each remaining row note the current holder (your competitor or a prospect), the framework flag, the renewals allowed and the expected window, and set a reminder at the start of the window to look for the follow-up call. The call itself appears as a new competition notice, often with a new title, so match by buyer and CPV rather than by the old name.

Why the estimate can be wrong

The window you compute is a model of how buyers behave, not a promise. Sell it to yourself as "worth a look", not "will be tendered on date X".

The fast route: GET /v1/tenders/expiring

The Re-tender Radar route returns award notices from TED and UK Find a Tender whose contract ends in a window (default today to 18 months on, at most 36 months), soonest first, one row per procedure and winner set. Each row has the buyer, CPV, the current holders (with their website when the notice states one), a value band (frameworks flagged), how the end date was derived (endBasis) and the expected window of the follow-up call: 9 months (12 for frameworks and contracts of three years or more) to 2 months before the end.

Calls on this page work without a key from one IP (10 requests a minute, 100 a day, short answers, point lookups). A free key lifts the limits: sign up. Routes, parameters and fields: OpenAPI.

curl

curl -s "https://api.arcadiumdata.io/v1/tenders/expiring?cpv=72&limit=1"

Response (abridged)

{
  "from": "2026-10-25",
  "to": "2028-04-25",
  "count": 1,
  "data": [{
    "id": "ted:216234-2026",
    "buyer": { "name": "Consejería de Digitalización", "country": "ES" },
    "cpvDivisionLabel": "IT services: consulting, software development, Internet and support",
    "holders": [{ "name": "INDRA SOLUCIONES TECNOLOGÍAS DE LA INFORMACIÓN, S.L.U.", "domain": null }],
    "value": { "eur": 954059.2, "basis": "award", "band": "100k_1m" },
    "isFramework": false,
    "awardedOn": "2026-03-30",
    "endOn": "2026-10-25",
    "endBasis": "concluded_plus_duration",
    "durationMonths": 7,
    "expectedRetender": { "from": "2026-01-25", "to": "2026-08-25" },
    "url": "https://ted.europa.eu/en/notice/-/detail/216234-2026"
  }]
}

This row shows the limits honestly: the end date comes from the conclusion date plus a seven-month duration, and the expected window already lies in the past, so the follow-up call is probably out or about to be. Use endBasis to rank rows (end_date is the most reliable) and from to look further ahead.

Filters: country, cpv (2 to 8 digits), from and to, minValueEur, frameworksOnly, and kind for the UK Procurement Act views (pipeline, terminated, changed). Anonymous calls need country or cpv (one value) and return at most five rows; a free key lifts that and adds paging.

curl

# Keyed: framework agreements ending in 2027, IT services, worth at least EUR 1 million
curl -s "https://api.arcadiumdata.io/v1/tenders/expiring?cpv=72&from=2027-01-01&to=2027-12-31&frameworksOnly=true&minValueEur=1000000&limit=50" \
  -H "Authorization: Bearer $ARCADIUM_API_KEY"

Python

import os
import requests

url = "https://api.arcadiumdata.io/v1/tenders/expiring?country=DE&cpv=72&limit=50"
headers = {"Authorization": "Bearer " + os.environ["ARCADIUM_API_KEY"]}
page = requests.get(url, headers=headers, timeout=60).json()
for row in page["data"]:
    names = ", ".join(h["name"] for h in row["holders"])
    print(row["endOn"], row["endBasis"], row["buyer"]["name"], "<-", names)

TypeScript

type Row = { endOn: string; endBasis: string | null; buyer: { name: string | null }; holders: { name: string }[]; url: string };

const res = await fetch('https://api.arcadiumdata.io/v1/tenders/expiring?country=GB&kind=terminated&limit=5');
const body = (await res.json()) as { data: Row[] };
for (const r of body.data) console.log(r.endOn, r.buyer.name, r.url);

Three ways to use it: a bidder filters a CPV division and a value band and gets a weekly shortlist (the saved search POST /v1/watch/tenders emails it); a sales team takes the current holders as a competitor list; an analyst compares expiry waves by country. The Re-tender Radar page shows the product; the tenders demo runs without sign-up.

Comparison

Build it from noticesArcadium Data /v1/tenders/expiring
SourcesTED API, Find a Tender, national portals, one by oneTED and UK Find a Tender in one schema
End dateYou derive it and track the basisendOn plus endBasis
Re-tender windowYour own ruleStated rule: 9 to 2 months before the end (12 for frameworks and 3+ years)
Current holders and websitesJoin winner names to companies yourselfHolders with website domain when the notice states one
FrameworksDetect with BT-765isFramework, value basis framework_ceiling
UK planned and terminated contractsRead UK1, UK3, UK10, UK11kind=pipeline|terminated|changed
AlertsBuild a schedulerPOST /v1/watch/tenders weekly email
Weak spotsMissing durations, renewalsSame data limits; shown in endBasis and renewalsMax

Get the data without the plumbing

Arcadium Data serves these datasets from one REST API and one MCP server, with sources, licences and dates on every answer. No key is needed to try the routes in this guide.

Sources

Last updated 2026-10-25. This guide is ours and independent of the organisations it describes. Found a mistake? Tell us and we correct it.

Questions

Is there an official list of public contracts that are about to expire?

No. Neither TED nor Find a Tender publishes one. You derive the end date from the award notice: a stated end date, or the start (or conclusion) date plus the duration.

How early does a buyer start the follow-up procurement?

It varies. Our API assumes a window from 9 months before the end to 2 months before (12 months for framework agreements and contracts of three years or more). Treat it as a model, not as a promise.

Which UK notices signal an upcoming re-tender?

UK1 (pipeline) and UK3 (planned procurement) list contracts a buyer will tender. UK7 states the contract period, UK10 can move the end date and UK11 ends a contract early.

Why does an expiring row show an expected re-tender window in the past?

The window is computed backwards from the end date. For short contracts or notices published late, the window has already passed, which usually means the follow-up call is out or imminent.

Can I get this without an API key?

Yes, in small doses: GET /v1/tenders/expiring with country or cpv returns up to five rows anonymously. A free key adds paging, all filters and full lists.

Cite as: Arcadium Data, "How to find public contracts that end soon: re-tender data from TED and Find a Tender", https://arcadiumdata.io/guides/contracts-ending-soon-re-tender-data/, data as of 2026-10-25.