Trovare gare d'appalto con la TED Search API

Che cos’è la TED Search API?

TED (Tenders Electronic Daily) è la gazzetta ufficiale dell’UE per gli appalti pubblici, dove viene pubblicato ogni avviso di gara sopra soglia di tutti i 27 Stati membri. La TED Search API espone questi avvisi come JSON tramite un unico endpoint:

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

Puoi chiamarla senza alcuna autenticazione, quindi è aperta a ogni riutilizzatore. Il riferimento completo di richiesta e risposta è nella documentazione Swagger.

Serve una chiave API?

Non per cercare. La Search API è completamente pubblica e una chiave aumenta solo i tuoi limiti di frequenza e sblocca altri endpoint, come la gestione dei tuoi avvisi. Se comunque ne vuoi una, bastano tre passaggi:

  1. Visita il TED Developer Portal.
  2. Accedi con le tue credenziali EU Login.
  3. Genera la chiave in Manage API Keys.

Cercare gare d’appalto con Python

Ogni richiesta contiene tre elementi: una query, i fields che vuoi ottenere e un limit. La query usa la sintassi della ricerca esperta di TED, scritta come field operator value e combinata con AND, OR e NOT. Questi sono i campi che userai più spesso:

CampoEsempioSignificato
classification-cpvclassification-cpv=38000000Codice CPV (settore)
buyer-countrybuyer-country=ITAPaese dell’amministrazione, ISO a 3 lettere
publication-datepublication-date>=20260420Pubblicato in una data o dopo
estimated-value-procestimated-value-proc>=50000Valore stimato dell’appalto

Invece di memorizzare la sintassi, costruisci la query visivamente nella ricerca esperta di TED e copia la stringa risultante nel tuo codice. Se non sei sicuro di quale codice CPV corrisponda al tuo settore, cercalo con la nostra ricerca dei codici CPV.

L’esempio seguente trova gare per attrezzature da laboratorio (CPV 38000000) pubblicate dall’inizio dell’anno, con un valore stimato tra 50.000 e 500.000 EUR, da amministrazioni in Italia. Poiché una singola pagina contiene al massimo 250 avvisi, scorre i risultati in modalità iterazione, recuperando cinque pagine da 100, cioè fino a 500 avvisi in una sola esecuzione:

import requests
import time

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

query = " AND ".join([
    "classification-cpv=38000000",     # Attrezzature da laboratorio
    "buyer-country=ITA",               # Italia
    "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):  # Recupera cinque pagine.
    data = requests.post(URL, json=payload).json()
    notices.extend(data["notices"])

    # Usa il token di iterazione per paginare in modo efficiente.
    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"]["ita"]
    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}")

Vale la pena conoscere un paio di dettagli sulla risposta. notice-title è indicizzato per codice lingua, quindi notice-title["ita"] restituisce il titolo in italiano, mentre classification-cpv arriva come elenco di codici. In modalità iterazione ogni pagina ti fornisce un iterationNextToken che rimandi nella richiesta successiva, e il ciclo si ferma appena una pagina torna più piccola del tuo limite. Da qui puoi sostituire ITA con qualsiasi codice paese, o 38000000 con il settore per cui partecipi.

Limiti e buone pratiche

La Search API è generosa, ma alcuni limiti ne condizionano l’uso:

  • Dimensione pagina. Una richiesta restituisce al massimo 250 avvisi, quindi qualcosa di più grande richiede paginationMode: "ITERATION" e il ciclo con token mostrato sopra.
  • Dimensione query. Una singola query può contenere fino a 2.000 clausole. Quando punti a molti codici CPV, raggruppali con IN (...) invece di concatenare una lunga serie di condizioni OR.
  • Limiti di frequenza. Le chiamate anonime sono limitate, quindi registra una chiave API prima di eseguire job pesanti o pianificati.
  • Forma dei campi. Molti campi sono multilingue o tornano come elenchi, ed è per questo che l’esempio li legge in modo difensivo con .get() e una chiave lingua.
  • Sii corretto. Memorizza nella cache ciò che recuperi ed evita di scaricare due volte gli stessi avvisi. TED si aggiorna durante la giornata, quindi una sincronizzazione oraria o giornaliera è di solito sufficiente.

Questo è l’intero ciclo: costruisci una query, inviala con POST, leggi il JSON e pagina. Da lì puoi salvare gli avvisi in un database, inviare notifiche al tuo team o integrarli direttamente nel tuo prodotto.