Developer guides

Bulgaria public procurement open data: CAIS EOP daily OCDS files

Bulgaria publishes every notice of its e-procurement system CAIS EOP as OCDS 1.1 under CC0 on the national open-data portal, one file per day, with no key. Source: Arcadium Data, data as of .

Bulgaria has one national electronic procurement platform, CAIS EOP (ЦАИС ЕОП, app.eop.bg), run by the Public Procurement Agency. The agency exports its notices as OCDS, which makes Bulgaria one of the easiest EU markets to read. It is also a large one by count: in the last twelve months we saw roughly 16,000 awards, many of them pharmaceutical tenders by hospitals.

The route has quirks: a POST API, a change of file layout in 2026, a publication lag, and the need to separate national notices from copies of EU ones.

The official route: data.egov.bg and the agency's files

The API

The portal data.egov.bg lists the agency (organisation id 502) and its datasets. Two anonymous POST calls get you the data: listDatasets returns datasets matching a filter, and getResourceData with a resource_uri returns the OCDS release package of one file. Use the API, not page crawls: the portal's robots.txt disallows crawling, and the API is what it offers for machines. The agency also publishes the same daily files on a public storage bucket (storage.eop.bg/open-data-YYYY-MM-DD/); check which of the two is fresher for you.

curl

# 1. list the agency's notice datasets
curl -s -X POST https://data.egov.bg/api/listDatasets \
  -H "content-type: application/json" \
  -d '{"criteria":{"keywords":"Автоматично генерирани данни за обявления ЦАИС ЕОП OCDS","org_ids":[502]},"records_per_page":100,"page_number":1}'

# 2. fetch one resource (a day) by its uri from the listing
curl -s -X POST https://data.egov.bg/api/getResourceData \
  -H "content-type: application/json" -d '{"resource_uri":"<uri from step 1>"}'

Python

import requests

API = "https://data.egov.bg/api"
body = {"criteria": {"keywords": "Автоматично генерирани данни за обявления ЦАИС ЕОП OCDS", "org_ids": [502]},
        "records_per_page": 100, "page_number": 1}
datasets = requests.post(API + "/listDatasets", json=body, timeout=60).json().get("datasets", [])
for d in datasets[:3]:
    print(d["name"])

# a resource is one OCDS release package
# pkg = requests.post(API + "/getResourceData", json={"resource_uri": uri}, timeout=120).json()["data"]
# for release in pkg["releases"]: ...

How the files are laid out

Until mid-2026 the agency published one dataset per fortnight with one resource each. Since June 2026 there is one dataset per month with one resource per day, named like "... на 01.09.2026 г.", appearing the next morning at about 6 MB and roughly 270 releases. Parse the day out of the resource name and handle both layouts if you want history. The newest month dataset appears with a lag: when the day you want is not listed yet, that is "not published yet", not "no notices", so retry later before you record an empty day.

Filtering and fields

Reading a release

Each release is one notice. The ocid joins every notice of one procedure, tender.id gives the number shown on app.eop.bg, buyer and parties name the contracting authority and the suppliers, tender.lots split a procedure into lots, and awards and contracts hold the result. As elsewhere in OCDS, a lot can have its own winner and value, so work per lot when you want accurate totals. Frameworks are common in Bulgarian hospital purchasing, and a framework value is a ceiling, not spend.

What the data looks like

Bulgaria has fewer buyers than Germany or France but publishes a very large number of notices, because hospitals, municipalities and state companies buy medicines, supplies and services in small lots. Medical supplies (CPV division 33) are the largest category by count, followed by construction (45) and services. Names are in Cyrillic, so keep UTF-8 end to end and normalise by case-folding. Company names carry legal forms such as ООД, ЕООД, АД and ЕАД; the form is a reliable way to separate companies from sole traders (ЕТ) before you publish winners.

A daily loop

Run once a day in the morning: list the datasets, pick the resource named for yesterday, fetch it, drop EU-directive releases, parse the rest, and upsert by release id. If yesterday is not listed, schedule a retry in a few hours and record the day as pending. Keep the raw package, about 6 MB a day, in cheap storage: the national filter may change and you will want to re-run it. A year of files is around 2 GB raw, so the traffic and the storage are small by the standards of tender data.

Pitfalls with values

Values are the weakest field. Estimated values are often missing for small purchases, awards state the contract value in one lot and a ceiling in another, and a few notices carry values with the wrong order of magnitude (a typed lev amount that is really a unit price). Do not sum raw values across a whole year without a sanity filter: cap each award at a multiple of the buyer's median, flag the rest, and report medians next to totals. The same care applies to winners: one supplier can appear under several spellings, so normalise case, quotes and the legal form before you rank suppliers, and prefer the company number (ЕИК) when a release has one. If you only need market sizes, work per CPV division and per buyer type, which are far more stable than single winner names.

Licence: CC0. The public notice page is https://app.eop.bg/today/<tender id>.

The fast route: Bulgaria next to every other EU market

Arcadium Data reads the daily files, handles both layouts, waits for lagging days, filters the TED copies and returns winners and buyers with CPV codes. The twelve-month summary for Bulgaria 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/markets?country=BG"

Python

import requests

data = requests.get("https://api.arcadiumdata.io/v1/tenders/markets?country=BG", timeout=30).json()
print(data["awards"], data["valueEur"], data["medianValueEur"])
for w in data["topWinners"]:
    print(w["name"], w["awards"])

TypeScript

const res = await fetch('https://api.arcadiumdata.io/v1/tenders/markets?country=BG');
const data = (await res.json()) as { awards: number; valueEur: number; topWinners: { name: string; awards: number }[] };
console.log(data.awards, data.valueEur);
for (const w of data.topWinners) console.log(w.name, w.awards);

Response (abridged)

{
  "awards": 16172, "awardsWithWinner": 12510, "frameworks": 2139,
  "valueEur": 11990220603, "medianValueEur": 83555,
  "concentration": { "level": "unconcentrated" },
  "topWinners": [{ "name": "СОФАРМА ТРЕЙДИНГ АД", "awards": 366 }, { "name": "ФЬОНИКС Фарма ЕООД", "domain": "phoenixpharma.bg", "awards": 308 }],
  "topBuyers": [{ "name": "ОБЩИНА БУРГАС", "awards": 397 }]
}

Add cpv=33 for medical supplies, the largest Bulgarian category. Notice search (GET /v1/tenders with country=BG) needs a free key. Region pages: Bulgarian tenders.

Comparison

data.egov.bg / EOP filesArcadium Data
KeyNoneNone for summaries; free key for notice search
FormatOCDS 1.1, one file per dayFlat notice schema
Layout changesFortnight then month datasetsHandled
LagNext morning; month dataset can trailRetries; days marked pending
TED copiesYou filter by legal basisFiltered
Named officialsIn partiesNever read
LicenceCC0Source URL kept

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.

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

Is Bulgarian procurement data open?

Yes. The Public Procurement Agency publishes CAIS EOP notices as OCDS 1.1 under CC0 on data.egov.bg, one file per day, with no key.

How do I download the daily OCDS files?

POST to data.egov.bg/api/listDatasets with the agency organisation id 502 and the notice keywords, then POST the resource uri to getResourceData. The agency also publishes the files on a public storage bucket.

Why is yesterday missing?

The month dataset can appear with a lag. A day that is not listed yet is not published yet, so retry later instead of recording zero notices.

Do the files include notices that are also in TED?

Yes. Releases with an EU-directive legal basis are also on TED. Skip them if you read TED, and keep the national-law ones.

Which currency do Bulgarian notices use?

Bulgaria adopted the euro on 1 January 2026, and older notices are in leva at the fixed rate of 1.95583 per euro. Read the currency field on each value.

Cite as: Arcadium Data, "Bulgaria public procurement open data: CAIS EOP daily OCDS files", https://arcadiumdata.io/guides/bulgaria-eop-open-data-tenders/, data as of 2026-10-25.