CPV codes explained: the list, the hierarchy and how to map free text to a code
The Common Procurement Vocabulary (CPV) is a set of about 9,450 codes (Regulation (EC) No 213/2008) that every EU public notice uses to describe what is bought; the first two digits are the division and a prefix match walks down the tree. Source: Arcadium Data, data as of .
If you work with public tenders, CPV is the join key: it is how you find every IT services contract in Poland or every road works notice in Spain, in any language. The codes look cryptic, but the structure is simple, the list is free, and mapping text to codes is a small search problem.
This guide shows the hierarchy, where the official list lives, a robust way to map free text to CPV, and the pitfalls in how buyers actually use the codes.
The official route: the vocabulary files and the structure
Where to get the list
The EU Publications Office publishes the CPV 2008 vocabulary, in all EU languages, as spreadsheets and XML on the SIMAP/TED pages, and on the EU Vocabularies site as SKOS and in machine-readable formats. Use the official files rather than copying a table from a blog: codes were revised in 2008 and since then only through formal amendments. The files also include the supplementary vocabulary (letter-prefixed codes such as AA01 for qualifiers); notices use the main vocabulary for the subject.
The structure
A code is 8 digits plus a check digit after a hyphen, as in 72200000-7 (software programming and consultancy). The check digit is for validation only; strip it before you store or filter. Reading from the left:
| Level | Digits | Example | Meaning |
|---|---|---|---|
| Division | First 2 | 72000000 | IT services: consulting, software development, Internet and support |
| Group | First 3 | 72200000 | Software programming and consultancy services |
| Class | First 4 | 72210000 | Programming services of packaged software products |
| Category | First 5 | 72212000 | Programming services of application software |
| Subcategory | Digits 6 to 8 | 72212100 | Industry specific software development services |
The tree is read by stripping trailing zeros. A prefix of 72 matches the whole division; 722 a group. There are 45 divisions, from 03 (agriculture) to 98 (other community, social and personal services). The main vocabulary splits into supplies (divisions 03 to 44, minus a few), works (45) and services (50 to 98); that is how you can tell what a notice buys without reading it.
Mapping free text to CPV
There is no official text classifier. A reliable recipe:
- Load the English labels of the whole list and lower-case them.
- Score each code by token overlap between your text and its label, weighting rarer words higher (BM25 works; plain TF-IDF is fine).
- Take the best 5 candidates, then roll up to the group when several siblings score close together. A precise code is often wrong where the group is right.
- Keep the top division as a coarse filter; it is the level where buyers are most consistent.
Python
import math, re
from collections import Counter
def tokens(s: str) -> list[str]:
return re.findall(r"[a-z]{3,}", s.lower())
def build(labels: dict[str, str]):
docs = {c: Counter(tokens(t)) for c, t in labels.items()}
df = Counter(w for d in docs.values() for w in d)
return docs, df, len(docs)
def best(text: str, labels: dict[str, str], k: int = 5):
docs, df, n = build(labels)
q = tokens(text)
score = {c: sum(d[w] * math.log(1 + n / df[w]) for w in q if w in d) for c, d in docs.items()}
return sorted(score.items(), key=lambda x: -x[1])[:k]
labels = {"72212000": "Programming services of application software", "72200000": "Software programming and consultancy services"}
print(best("custom application software development", labels))
Which divisions matter
Most users need a handful of divisions: 72 (IT services), 48 (software packages), 30 and 32 (computers and communications equipment), 45 (construction work), 71 (architecture and engineering), 79 (business services), 80 (education), 85 (health and social work), 90 (sewage, refuse, cleaning and environmental services), 33 (medical equipment and pharmaceuticals), 34 (transport equipment) and 60 (transport services). A good first filter for a supplier is two or three divisions, then narrow to groups once you see real notices. Do not start from the 9,000 leaf codes: buyers pick them inconsistently, and the narrower the code, the lower the recall. Review the filter every quarter by sampling twenty notices that match and twenty that do not.
Common mistakes
- Matching on the full code only. A tender for software may use
72200000or72212000. Match by prefix at the level you need and say which level in the report. - Mixing versions. CPV 2008 is the only version in use, but older tools carry labels from earlier drafts. Take labels from the official file for the language you display.
- Ignoring the notice language. Buyers use the official file in their language; the code is the same, the label is not. Join on the code, never on the label.
- Trusting the main code. The main CPV is often a catch-all, and the real subject sits in the additional codes or the lot.
How buyers really use CPV
- A notice lists a main CPV and many extra ones; a construction tender may list thirty. Filter on any code, not only the first, or you lose recall.
- Buyers choose broad codes when unsure. Roll results up to division when you count markets.
- Different notice types store CPV in different places (TED eForms: main and additional classification on the procedure and lots; OCDS:
tender.classificationand item classifications). - Lots can differ from the procedure. For lot-level work, take codes from the lot.
The fast route: CPV as a filter, with labels
In the Arcadium Data API every tender route takes a cpv parameter that accepts a division, group or full code and matches by prefix across all notice sources. The response also carries the division label. The leader board for IT services (cpv=72) over the last twelve months is open without a 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/leaders?cpv=72&limit=2"
Python
import requests
data = requests.get("https://api.arcadiumdata.io/v1/tenders/leaders?cpv=72&limit=2", timeout=30).json()
print(data["filter"]["cpvLabel"], data["awards"], data["valueEur"])
for w in data["winners"]:
print(w["name"], w["country"], w["awards"])
TypeScript
const res = await fetch('https://api.arcadiumdata.io/v1/tenders/leaders?cpv=72&limit=2');
const data = (await res.json()) as { filter: { cpvLabel: string }; awards: number; winners: { name: string; country: string; awards: number }[] };
console.log(data.filter.cpvLabel, data.awards);
for (const w of data.winners) console.log(w.name, w.country, w.awards);
Response (abridged)
{
"filter": { "cpv": ["72"], "cpvLabel": "IT services: consulting, software development, Internet and support" },
"awards": 22717, "valueEur": 245357921177,
"winners": [{ "name": "UAB Bitė Lietuva", "country": "LT", "awards": 94 }],
"buyers": [{ "name": "Dirección General de Racionalización y Centralización de la Contratación", "country": "ES", "awards": 329 }]
}
Combine cpv with country on GET /v1/tenders/markets for a market size, or with GET /v1/tenders/expiring for contracts that end soon. Our tender pages are organised by country and CPV division.
Comparison
| Official files and DIY mapping | Arcadium Data | |
|---|---|---|
| Code list | Free XLS, XML and SKOS in all languages | Division labels in responses |
| Filter by code | You index the notices | cpv prefix on every tender route |
| Several CPV per notice | You handle it | Matches any code |
| Free text to CPV | Build a scorer | Not offered; use division codes |
| Cost | Your time | Free tier; 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
- TED API guide
- Contracts Finder API in depth
- Find public contracts that end soon
- Public tenders by country and sector
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
How many CPV codes are there?
About 9,450 in the main vocabulary of CPV 2008, in a tree of 45 divisions. A supplementary vocabulary adds letter-prefixed qualifier codes.
Where can I download the CPV code list?
From the EU Publications Office: the SIMAP/TED CPV pages have spreadsheets and XML in all EU languages, and the EU Vocabularies site has machine-readable versions.
What does the last digit of a CPV code mean?
It is a check digit, written after a hyphen. It validates the first eight digits and is not part of the hierarchy. Strip it before filtering.
How do I map a product description to a CPV code?
Score the description against the code labels (BM25 or TF-IDF), take the top candidates, and roll up to the group or division when several siblings score alike.
Can a tender have more than one CPV code?
Yes. Notices list a main CPV and often many additional ones, and lots can differ from the procedure. Filter on any code to avoid losing notices.
Cite as: Arcadium Data, "CPV codes explained: the list, the hierarchy and how to map free text to a code", https://arcadiumdata.io/guides/cpv-codes-list-hierarchy-mapping/, data as of 2026-10-25.