The EU publishes thousands of public procurement notices every week on TED (Tenders Electronic Daily). Commercial tender alert services usually charge a subscription. If you already know which CPV codes you bid on, the official Search API is enough for a personal alert.
I put that into one Python file with no dependencies beyond the standard library: ted-tender-alerts (MIT).
python ted_alerts.py --cpv 72 48 --country AUT --days 7 --lang de
22 matching notices, 22 new.
- Commvault
… | AUT | 21,000,000 EUR | deadline 2026-11-09 (32 d)
https://ted.europa.eu/de/notice/-/detail/696492-2026
- MA 9 – Digitalisierung 2027-2031
Magistrat der Stadt Wien … | AUT | deadline 2026-11-17 (40 d)
…
(Real output from 8 October 2026. Run it again and you get 22 matching notices, 0 new.)
What the API gives you
TED Search API v3 is anonymous. One POST to https://api.ted.europa.eu/v3/notices/search with a query string in TED's expert-search language:
query = (
"publication-date>=20261001"
" AND buyer-country IN (AUT DEU)"
" AND notice-type IN (cn-standard cn-social cn-desg subco qu-sy)"
" AND (classification-cpv=72* OR classification-cpv=48*)"
" SORT BY publication-date DESC"
)
body = {"query": query, "fields": [...], "page": 1, "limit": 100,
"paginationMode": "PAGE_NUMBER", "scope": "ALL"}
CPV prefixes work: 72 matches every code under 72000000 (IT services). Countries use ISO alpha-3 (AUT, DEU). --print-query prints the string so you can paste it into TED's expert search and compare.
The script is gentle with the API: it pauses 0.5 s between result pages and stops after 20 pages (100 notices each) by default.
Only the new ones
A state file (ted_state.json) remembers publication numbers already reported. Every run compares the current result to that set and prints only the difference. Notices older than 120 days drop out of the state, so the file stays small.
Same idea for an Atom feed of the latest 100, a CSV you append to, a Markdown table, or an e-mail through your own SMTP server (SMTP_HOST, MAIL_TO, … — the password is never printed or written).
Run it daily for free on GitHub Actions
The repository includes an example workflow (examples/daily-ted-alerts.yml). Copy it to .github/workflows/ in your own copy of the repo and it runs every weekday morning, commits the state and the Atom feed back, and optionally e-mails you. Edit the CPV/country filters in the YAML; add SMTP secrets if you want mail. Point a feed reader at the raw URL of data/feed.xml (public repos) or just open data/latest.md.
Finding your CPV codes
Look at five tenders you would have bid on and note their CPV codes (they're on every TED notice). Shorter prefixes catch more: 72 is all IT services, 7222 only IT consultancy. Start broad, then narrow once you see the noise.
Legal note
Data comes from TED – Tenders Electronic Daily, © European Union. Notices may be reused free of charge with attribution (TED legal notice). The script adds the attribution line to every output.
Try it
git clone https://github.com/philippprimisser-max/ted-tender-alerts
cd ted-tender-alerts
python ted_alerts.py --cpv 72 --country AUT --days 3
AI-assisted: a model helped with the wording. The sample run and the 22-notice count are from my own machine on 8 October 2026. Not affiliated with the EU Publications Office.
Top comments (2)
how does the script handle API rate limits and pagination when a CPV query returns thousands of entries, and does it implement any retry/backoff for the occasional 500 from the TED endpoint also consider persisting the last seen notice id to avoid duplicate alerts after a restart
Some comments may only be visible to logged-in visitors. Sign in to view all comments.