How to find who won a government contract: award data from TED, Find a Tender and national portals
Winners of public contracts are published in award notices on TED, Find a Tender and national portals, free of charge; the work is in matching a winner name to a company, handling frameworks and joint bids, and leaving out private persons. Source: Arcadium Data, data as of .
"Who won the contract" is a question journalists, rival bidders, sales teams and investors ask all the time. The answer is public by law: EU procurement rules oblige buyers above the thresholds to publish a contract award notice, and UK buyers do the same under the Procurement Act 2023. The notices are free to read and reuse. They are also inconsistent: the winner may be missing, spelled three ways, a joint venture or a sole trader.
This guide shows the official sources, a minimal reader, and the traps. Then it shows the shortcut: supplier profiles and winner lookups by company website.
The official route: award notices
Where to look
- TED (Tenders Electronic Daily) for the EU, EEA and associated procurement above the EU thresholds. The Search API is anonymous. Our TED API guide has the request, the limits and the eForms fields.
- Find a Tender and Contracts Finder for the UK, both OCDS. See the UK guide.
- National portals for contracts below the EU thresholds: each country has its own (France: BOAMP, Germany: the federal and state portals, Poland: BZP, Bulgaria: CAIS EOP, and so on), with different formats, languages and licences. Many mirror above-threshold notices that also appear on TED.
A minimal winner reader for TED
The eForms fields to ask for: winner-name, winner-internet-address, result-value-notice (and per lot total-value, tender-value), buyer-name, buyer-country and classification-cpv. Notice types for results start with can- (contract award notice).
curl
curl -s https://api.ted.europa.eu/v3/notices/search \
-H 'Content-Type: application/json' \
-d '{"query":"publication-date=20261023 AND notice-type=can-standard AND winner-name=*","fields":["publication-number","buyer-name","buyer-country","winner-name","winner-internet-address","result-value-notice","classification-cpv"],"limit":250,"page":1,"paginationMode":"PAGE_NUMBER"}'
Python
import re
import requests
def pick(obj, lang_order=("eng",)):
"""TED text fields are objects keyed by 3-letter language, each a list. Return one string."""
if isinstance(obj, dict):
for lang in (*lang_order, *obj.keys()):
if obj.get(lang):
return obj[lang][0]
return None
if isinstance(obj, list):
return obj[0] if obj else None
return obj
body = {
"query": "publication-date=20261023 AND notice-type=can-standard AND winner-name=*",
"fields": ["publication-number", "buyer-name", "winner-name", "winner-internet-address", "result-value-notice"],
"limit": 250, "page": 1, "paginationMode": "PAGE_NUMBER",
}
r = requests.post("https://api.ted.europa.eu/v3/notices/search", json=body, timeout=60)
r.raise_for_status()
for n in r.json()["notices"]:
winners = n.get("winner-name") or []
names = [pick(w) if isinstance(w, dict) else w for w in (winners if isinstance(winners, list) else [winners])]
# Drop what could be a private person before you publish: keep names with a company form.
firm = [x for x in names if x and re.search(r"\b(GmbH|AG|S\.?A\.?|S\.?L\.?|SRL|Ltd|Limited|B\.?V\.?|N\.?V\.?|AB|AS|Oy|Sp\. z o\.o\.)\b", x, re.I)]
print(n["publication-number"], firm)
TypeScript
const body = {
query: 'publication-date=20261023 AND notice-type=can-standard AND winner-name=*',
fields: ['publication-number', 'buyer-name', 'winner-name', 'winner-internet-address', 'result-value-notice'],
limit: 250,
page: 1,
paginationMode: 'PAGE_NUMBER',
};
const res = await fetch('https://api.ted.europa.eu/v3/notices/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const { notices } = (await res.json()) as { notices: Record<string, unknown>[] };
console.log(notices.length, 'award notices with a winner name');
Joining a winner name to a company
A name alone is a weak key. Use these signals in order, and store which one matched: (1) a website stated in the notice (TED: winner-internet-address), which is the strongest because the buyer or winner typed it; (2) a registry identifier stated in the notice (TED: winner-identifier; UK OCDS: parties[].identifier with scheme GB-COH), checked against the register; (3) an exact legal-name match inside one country, after you strip legal forms and punctuation. Do not match on name across countries: "Siemens" is a group of dozens of legal entities, and a Portuguese subsidiary winning a rail contract is not the Munich parent. Keep the entity that won and the group it belongs to as two separate fields.
What you can honestly say
A winners table built from notices is a lower bound. Some awards are never published (below-threshold or exempt contracts), some notices omit the winner, and values are partial. Say "won at least N contracts worth at least EUR X in the notices we read between these dates", not "earned". Notices also appear days to weeks after the decision, so the latest month is always incomplete.
Traps
- Many awards name no winner. A lot can close without an award, and buyers may defer some fields. Count them separately so your "top winners" table is not silently short. Our TED guide states the share from our own data.
- Names are not identifiers. The same company appears as "Siemens AG", "SIEMENS Aktiengesellschaft" and a local subsidiary. Match on a registry id or a website, not on a string.
- Joint bids. A notice with several winners should count once for each, and its value should be split, not duplicated.
- Framework agreements. The stated value is a ceiling for all call-offs, not what the winner earned. Do not add it to a spend total.
- Values per lot, per tender, per notice, in different currencies, sometimes absurd (a typo adds three zeros). Sanity-check before ranking.
- Sole traders are private persons. A winner name that looks like a person is personal data. Do not publish it without a lawful basis; many legal departments simply drop such rows.
- Duplicates. The same notice appears on a national portal and on TED. Deduplicate by procedure and buyer.
- Timing. The notice follows the award by days to weeks, and a standstill period can precede the signed contract.
The fast route: winners by company, supplier profiles, leaders
Arcadium Data keeps the award notices of TED, Find a Tender and the national boards in one deduplicated table, with winners matched to company websites where the notice states one, frameworks flagged and winner names that could be private persons withheld (winnersWithheld counts them).
All awards a company won, by its website, with no key:
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?winnerDomain=siemens.com&limit=1"
Response (abridged)
{
"count": 1,
"data": [{
"id": "ted:726751-2026",
"source": "ted",
"kind": "award",
"buyer": { "name": "Infraestruturas de Portugal S.A.", "country": "PT" },
"cpvCodes": ["71311230"],
"awardValue": { "amount": 498817, "currency": "EUR", "eur": 498817 },
"winners": ["SIEMENS MOBILITY, UNIPESSOAL, LDA"],
"winnerDomains": ["siemens.com"],
"winnersWithheld": 0,
"publishedOn": "2026-10-21",
"url": "https://ted.europa.eu/en/notice/-/detail/726751-2026"
}]
}
A supplier profile aggregates every public award of one supplier since October 2023: buyer concentration (buyerHhi, top-buyer share), sectors, countries, monthly timeline, contracts ending in the next 18 months and the 20 latest awards. Frameworks and implausible values are left out of the totals, and co-winners split a value equally.
curl
curl -s "https://api.arcadiumdata.io/v1/tenders/suppliers/siemens.com"
Who wins most in a country or CPV division, for the current month and the eleven before, from GET /v1/tenders/leaders (anonymous preview of five rows per list; it needs country, cpv or q). The free tender winners tool runs the same query in the browser.
Python
import requests
def awards_won(domain: str):
r = requests.get("https://api.arcadiumdata.io/v1/tenders?winnerDomain=" + domain + "&limit=5", timeout=60)
r.raise_for_status()
for n in r.json()["data"]:
value = (n.get("awardValue") or {}).get("eur")
yield n["publishedOn"], n["buyer"]["name"], value, n["url"]
for row in awards_won("siemens.com"):
print(row)
TypeScript
type Award = { publishedOn: string; buyer: { name: string | null }; awardValue: { eur: number | null } | null; url: string };
const res = await fetch('https://api.arcadiumdata.io/v1/tenders?winnerDomain=siemens.com&limit=5');
const { data } = (await res.json()) as { data: Award[] };
for (const a of data) console.log(a.publishedOn, a.buyer.name, a.awardValue?.eur, a.url);
Be clear about what you get. A winner domain is only set when the notice names one winner and one website (or our matching ties the company to a site), so the share of winners with a domain is far lower in the UK than in countries whose notices state websites. A profile is built from public notices and is not a statement about the company's total revenue. Search by name or buyer needs a free key.
Comparison
| Read notices yourself | Arcadium Data tenders API | |
|---|---|---|
| Sources | One integration per portal | TED, Find a Tender, Contracts Finder and national boards |
| Winner to company | Name matching by hand | winnerDomains and supplier profile by domain |
| Duplicates across portals | You find them | Listed once |
| Frameworks and values | You flag and sanity-check | Flagged; implausible values left out of totals |
| Private persons | Your responsibility | Withheld, counted in winnersWithheld |
| Who wins most in a market | Aggregate yourself | /v1/tenders/leaders |
| Cost | Engineering time | Anonymous lookups free; free key for search |
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.
Related
- Tender winners tool
- Public contract winners
- TED API guide
- BOAMP France
- German public tenders as data
- Poland BZP
- Bulgaria CAIS EOP
- UK Find a Tender API
- Find contracts that end soon
Sources
- TED API documentation
- TED: the EU public procurement journal
- Find a Tender
- Open Contracting Data Standard
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
Where can I see who won a government contract?
In the contract award notice. For the EU that is TED, for the UK Find a Tender and Contracts Finder, and for smaller contracts the national portal of the country. All are free to read.
Why does an award notice show no winner?
A lot can close without an award, the buyer may defer some fields, or the winner name is not stated. In TED these notices exist in noticeable numbers, so count them separately.
Can I see how much a company earned from public contracts?
Only approximately. Notices state awarded values per contract; frameworks state a ceiling and call-offs are not always published. Treat totals as a lower bound and split joint awards.
Are sole traders shown as winners?
Not by us. Winner names that could be private persons are withheld and counted in winnersWithheld, because they are personal data.
How do I find all contracts won by one company?
Call GET /v1/tenders?winnerDomain=<domain> for the notices, or GET /v1/tenders/suppliers/<domain> for the profile. Both work without a key in small doses.
Cite as: Arcadium Data, "How to find who won a government contract: award data from TED, Find a Tender and national portals", https://arcadiumdata.io/guides/who-won-government-contracts-data/, data as of 2026-10-25.