Find tenders with TED Search API

What is the TED Search API?

TED (Tenders Electronic Daily) is the EU’s official journal for public procurement, where every above-threshold tender notice from all 27 member states is published. The TED Search API exposes those notices as JSON through a single endpoint:

POST https://api.ted.europa.eu/v3/notices/search

You can call it without any authentication, so it is open to every reuser. The complete request and response reference lives in the Swagger documentation.

Do you need an API key?

Not to search. The Search API is fully public, and a key only raises your rate limits and unlocks other endpoints, such as managing your own notices. If you do want one, it takes three steps:

  1. Visit the TED Developer Portal.
  2. Log in with your EU Login credentials.
  3. Generate your key under Manage API Keys.

Search tenders with Python

Every request carries three things: a query, the fields you want back, and a limit. The query itself uses TED’s expert search syntax, written as field operator value and combined with AND, OR, and NOT. These are the fields you will reach for most often:

FieldExampleMeaning
classification-cpvclassification-cpv=38000000CPV code (sector)
buyer-countrybuyer-country=DEUBuyer country, ISO 3-letter
publication-datepublication-date>=20260420Published on or after a date
estimated-value-procestimated-value-proc>=50000Estimated value of the procurement

Rather than memorize the syntax, build your query visually in the TED expert search and copy the resulting query string into your code. If you are unsure which CPV code fits your sector, look it up with our CPV code search.

The example below finds laboratory-equipment tenders (CPV 38000000) published since the start of the year, with an estimated value between 50,000 and 500,000 EUR, from buyers in Germany. Because a single page holds at most 250 notices, it pages through results with iteration mode, pulling five pages of 100 for up to 500 notices in one run:

import requests
import time

URL = "https://api.ted.europa.eu/v3/notices/search"

query = " AND ".join([
    "classification-cpv=38000000",     # Lab equipment
    "buyer-country=DEU",               # Germany
    "publication-date>=20260101",
    "estimated-value-proc>=50000",
    "estimated-value-proc<=500000",
])

payload = {
    "query": query,
    "fields": [
        "publication-number",
        "notice-title",
        "classification-cpv",
        "estimated-value-proc",
        "estimated-value-cur-proc",
    ],
    "limit": 100,
    "scope": "ACTIVE",
    "paginationMode": "ITERATION",
}

notices = []
for _ in range(5):  # Fetch five pages.
    data = requests.post(URL, json=payload).json()
    notices.extend(data["notices"])

    # Use iteration token to paginate results efficiently.
    token = data.get("iterationNextToken")
    if not token or len(data["notices"]) < payload["limit"]:
        break
    payload["iterationNextToken"] = token

    time.sleep(1)

for notice in notices:
    number = notice["publication-number"]
    title = notice["notice-title"]["eng"]
    cpv = ", ".join(notice["classification-cpv"])
    value = notice.get("estimated-value-proc", "n/a")
    currency = notice.get("estimated-value-cur-proc", "")

    print(f"{number} | {title} | {value} {currency} | {cpv}")

A couple of response details are worth knowing. notice-title is keyed by language code, so notice-title["eng"] returns the English title, while classification-cpv arrives as a list of codes. In iteration mode every page hands you an iterationNextToken that you pass back on the next request, and the loop stops as soon as a page comes back smaller than your limit. From here you can swap DEU for any country code, or 38000000 for the sector you bid on.

Limits and good practice

The Search API is generous, but a handful of limits shape how you use it:

  • Page size. A request returns at most 250 notices, so anything larger needs paginationMode: "ITERATION" and the token loop shown above.
  • Query size. A single query can hold up to 2,000 clauses. When you target many CPV codes, group them with IN (...) rather than chaining a long list of OR terms.
  • Rate limits. Anonymous calls are throttled, so register an API key before you run heavy or scheduled jobs.
  • Field shapes. Many fields are multilingual or come back as lists, which is why the example reads them defensively with .get() and a language key.
  • Be a good citizen. Cache what you fetch and avoid pulling the same notices twice. TED refreshes throughout the day, so an hourly or daily sync is usually plenty.

That is the entire loop: build a query, POST it, read the JSON, and paginate. From there you can store notices in a database, push alerts to your team, or feed them straight into your own product.