BOAMP open data API: French public tenders and how to dedupe against TED
France publishes BOAMP, its national procurement bulletin, as open data on Opendatasoft with no key and the Licence Ouverte 2.0; the JOUE family in it is a French copy of a TED notice and must be skipped when you also read TED. Source: Arcadium Data, data as of .
The Bulletin officiel des annonces des marchés publics (BOAMP) is run by DILA and carries notices of French public buyers: the national procedures between about EUR 90,000 and the EU thresholds, the "procedure adaptee" (MAPA) below that, concessions, and French copies of notices that also go to the EU's TED. Its open dataset has history from 2015.
The data is good and easy to reach. The catches are the row cap on one query, a notice body that contains personal data, and the double counting with TED.
The official route: Opendatasoft records API
The endpoint
GET https://boamp-datadila.opendatasoft.com/api/explore/v2.1/catalog/datasets/boamp/records is anonymous. Filters use the Opendatasoft query language: where, select, order_by, limit (100 a call) and offset. Offset plus limit cannot pass 10,000 on one query, so slice by publication day. A day holds a few hundred national notices, well under the cap. For a full historical copy use the dataset's exports endpoint instead of paging.
curl
curl -s -G "https://boamp-datadila.opendatasoft.com/api/explore/v2.1/catalog/datasets/boamp/records" \
--data-urlencode "where=dateparution = date'2026-10-23' AND famille != 'JOUE'" \
--data-urlencode "select=idweb,objet,famille,nature,dateparution,datelimitereponse,nomacheteur,code_departement,url_avis" \
--data-urlencode "limit=100"
Python
import requests
URL = "https://boamp-datadila.opendatasoft.com/api/explore/v2.1/catalog/datasets/boamp/records"
def boamp_day(day: str):
offset = 0
while offset < 10_000:
r = requests.get(URL, timeout=60, params={
"where": f"dateparution = date'{day}' AND famille != 'JOUE'",
"select": "idweb,objet,famille,nature,dateparution,nomacheteur,url_avis",
"limit": 100, "offset": offset,
})
rows = r.json().get("results", [])
yield from rows
if len(rows) < 100:
return
offset += 100
for row in boamp_day("2026-10-23"):
print(row["idweb"], row["nature"], row["nomacheteur"])
The fields worth keeping
| Field | Meaning |
|---|---|
idweb | The notice id; the public page is url_avis |
famille | FNS (national, up to the EU thresholds), MAPA, DSP (concessions) or JOUE (copy of a TED notice) |
nature, nature_categorise | Call for tender, award, rectification, and so on |
objet | Title of the procurement, in French |
nomacheteur, code_departement | Buyer name and department code (the region key) |
datelimitereponse | Deadline for offers |
annonce_lie | Linked notices; joins an award to its call |
donnees | The full notice as JSON: CPV, amounts, winners, but also people |
Personal data in donnees
The donnees JSON holds contact persons, phone numbers and email addresses (keys such as correspondantPRM, nomContact and, in notices before 2024, CORRESPONDANT, TEL, MEL). Read only what you need: CPV codes, estimated and awarded amounts, and the winner's company name. Winners can also be natural persons (PersonnePhysique); skip those. Notices before 2024 use an older schema with upper-case keys and values like {"#text": ..., "@DEVISE": ...}, so write two parsers or start in 2024.
Deduplicating against TED
French buyers above the EU thresholds publish in the Official Journal, and BOAMP keeps a French copy under famille = JOUE. If you read TED as well, filter that family out and take above-threshold notices from TED, which has the richer eForms data. Below the thresholds, BOAMP is the only source. After that filter the two do not overlap in our data. Winners are often withheld in French notices (a large share of awards have no named winner), so do not read a missing name as an error.
Keeping the copy current
A notice is not final when it is published. Buyers issue rectifications (nature contains "rectificatif") and award notices that point back to the call through annonce_lie. A daily job should therefore query the last three or four publication days again, not only yesterday, and upsert by idweb. Store the raw record next to your parsed row; the schema changed once and may change again.
What you can build from it
- An opportunity feed by department (
code_departement) and keyword inobjet, with the deadline indatelimitereponse. Below the EU thresholds this is the only complete source, and small and mid-size suppliers live in that range. - A winners list from award notices: take the company name from
donnees, skip natural persons, and join the name to the French company register by name and place. The register is open and covered in our registers guide. - A buyer profile: how often a buyer publishes, which procedure types it uses and in which months. Many French buyers publish in waves at the start of the budget year.
Two checks before you trust a count
First, reconcile with the source: pick one day, count the records the API returns for it with the family filter on and off, and compare with what the BOAMP site shows for that day. A mismatch usually means a time zone slip in the date filter or a family you forgot. Second, watch for the same procedure published twice by the same buyer, once as MAPA and once after a correction; group by buyer, object and date window before you report the number of tenders in a department. French buyers also publish "avis d'attribution" for contracts that were never advertised, so awards can outnumber calls in a quarter without any error. Finally, record the date of your last full reconciliation in your own pipeline notes; a quarterly check against the BOAMP website catches silent schema changes early. Use the department code to build regional views, and remember that overseas departments and some national buyers carry codes that are not metropolitan departments.
Licence: Licence Ouverte / Etalab 2.0. Attribute DILA and keep url_avis.
The fast route: French notices with TED merged
Arcadium Data reads BOAMP daily next to TED, skips the JOUE copies, parses both notice schemas, drops personal fields, and returns buyers, CPV codes, values in EUR and the department. The one-year market summary for France 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=FR"
Python
import requests
data = requests.get("https://api.arcadiumdata.io/v1/tenders/markets?country=FR", timeout=30).json()
print(data["awards"], data["awardsWithWithheldWinner"], data["frameworks"])
for b in data["topBuyers"]:
print(b["name"], b["awards"])
TypeScript
const res = await fetch('https://api.arcadiumdata.io/v1/tenders/markets?country=FR');
const data = (await res.json()) as { awards: number; frameworks: number; topBuyers: { name: string; awards: number }[] };
console.log(data.awards, data.frameworks);
for (const b of data.topBuyers) console.log(b.name, b.awards);
Response (abridged)
{
"awards": 41799, "awardsWithWinner": 23497, "awardsWithWithheldWinner": 21128,
"frameworks": 18157, "valueEur": 85179274320,
"topWinners": [{ "name": "IDVERDE", "awards": 119 }, { "name": "OTIS", "domain": "otis.com", "awards": 118 }],
"topBuyers": [{ "name": "EDF SA", "awards": 204 }, { "name": "Bordeaux Métropole", "awards": 184 }]
}
GET /v1/tenders/expiring?country=FR lists French contracts by end date and expected re-tender window, and a free key opens GET /v1/tenders with country=FR. See also the French company registers and tenders by sector.
Comparison
| BOAMP direct | Arcadium Data | |
|---|---|---|
| Key | None | None for summaries; free key for notice search |
| Row cap | 10,000 per query; slice by day or use exports | Cursors |
| TED copies | Family JOUE; you filter | Filtered; TED read once |
| Schemas | Two (before and after 2024) | One |
| Personal data in notices | In the donnees field | Never read |
| Language | French | English titles for TED, original text kept |
| Licence | Licence Ouverte 2.0 | Source URL kept on every row |
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
- Find public contracts that end soon
- CPV codes explained
- Company registers in Europe
- French tenders
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 the BOAMP API free?
Yes. The BOAMP dataset on Opendatasoft needs no key and is published under the Licence Ouverte (Etalab 2.0). Attribute DILA and link the notice.
How many rows can I get from one BOAMP query?
Opendatasoft limits offset plus limit to 10,000 on a records query, with 100 rows per call. Slice by publication day, or use the dataset exports endpoint for bulk.
How do I avoid duplicates between BOAMP and TED?
Skip BOAMP rows with famille JOUE, which are French copies of TED notices, and take above-threshold notices from TED. Below the thresholds BOAMP is the only source.
Does BOAMP contain personal data?
The donnees field holds contact persons, phones and emails, and some winners are natural persons. Read only CPV, amounts and company winners, and drop the rest.
Why do many French awards have no winner?
Notices often withhold the winner or publish it in a separate notice. Treat a missing winner as unknown, and use the linked notice field to look for the award notice.
Cite as: Arcadium Data, "BOAMP open data API: French public tenders and how to dedupe against TED", https://arcadiumdata.io/guides/boamp-api-france-public-tenders/, data as of 2026-10-25.