DECP Win Intelligence
Under maintenancePricing
from $50.00 / 1,000 competitor-dossiers
DECP Win Intelligence
Under maintenanceFrench public-procurement (DECP) market intelligence: competitor dossiers, market benchmarks and buyer histories over the consolidated data.gouv.fr dataset, served from a normalized cache.
Pricing
from $50.00 / 1,000 competitor-dossiers
Rating
0.0
(0)
Developer
Adrien
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
DECP Win Intelligence — who won French public contracts
Query France's public-procurement award data (DECP) as clean, structured JSON. One identifier in — a company SIREN, a buyer SIRET, or a CPV code plus a department — one auditable JSON answer out: competitors, price benchmarks, buyer behaviour.
The award data itself is open and free. What this Actor sells is the hard part: turning a 3.1-million-row, trap-filled consolidated file into answers you can trust, with a per-record quality score and every source market listed for audit.
What you get in 30 seconds
Three query modes, each returning one JSON object (agent- and spreadsheet-friendly):
| Mode | You give | You get back |
|---|---|---|
| competitor-dossier | a company siren | its markets, top buyers, CPV mix, yearly trend, weighted win-rate |
| market-benchmark | a cpv prefix + a departement | price distribution (q1 / median / q3), top suppliers, top buyers on that segment |
| buyer-history | a buyer siret | its markets, recurring CPV, favourite suppliers, monthly seasonality |
Every response carries a qualite block (how many amounts were kept vs set aside,
and why), a data_version date, and the complete list of source market IDs so
any number can be re-checked against the open data.
Concrete use cases
1. Competitive analysis — "how dominant is this company, and where?"
Call competitor-dossier on a SIREN. Example: ENEDIS (SIREN 444608442).
- 130 markets, total €3,291,593,920 — but the median market is €16,272.87.
The gap is not a bug:
montant_top1_part = 0.996tells you a single €3.28 bn market makes up 99.6 % of the total. You see the concentration instead of being misled by the headline sum. - Weighted win-rate = 0.69 % across 60 CPV×department segments — an
approximate share of awards where the company is actually active (definition and
honest limits below), with a
segments_topbreakdown so you can read exactly which CPV division and department drive it. - Recurring buyers: EPFL Béarn Pyrénées (
53042890300013, 40 markets), Région Île-de-France (23750007900312, 11 markets), and so on — a ready-made account map.
2. Price benchmarks — "what does this kind of contract go for, in this area?"
Call market-benchmark with a CPV prefix and a department. Example: CPV 71
(engineering & architecture services) in Corse-du-Sud (2A).
- 518 awards, price distribution q1 €45,655 · median €97,874 · q3 €300,000
(min €1,400, max €6,435,734). Sentinel and missing amounts are excluded from these
statistics and reported separately (
n_ecartes), so the quartiles are clean. - Top suppliers on the segment: Cabinet Sibella & Associés (
532822202, 26 markets), Artelia (444523526, 19), Rocca e Terra (792993602, 19). - Top buyers: Collectivité de Corse (
20007695800012, 261 markets) far ahead of the rest.
Benchmarks are always scoped to one CPV prefix × one department — a national aggregate is deliberately out of scope (see The three modes).
3. B2B commercial signals — "how does this buyer buy, and from whom?"
Call buyer-history on a buyer SIRET. Example: a French local-authority buyer,
SIRET 21690271800016.
- 150 markets, total €61,096,071, median €91,437.
- Favourite suppliers: Orange (
380129866, 6 markets), Parcs et Sports (329263164, 5), Engie Énergie Services (552046955, 4). - Seasonality: award activity peaks in May and June (20 markets each) — useful timing for outreach and pipeline planning.
4. Unit queries for AI agents
Each mode is a single deterministic call → single JSON dict. No pagination to stitch, no HTML to scrape, no login. That shape is built for tool-use: an agent passes one identifier and gets a bounded, well-typed object it can reason over. See Using it from an AI agent below.
The three modes
The Apify input mode enum is hyphenated (competitor-dossier,
market-benchmark, buyer-history); the output mode field mirrors the domain
function name with an underscore (competitor_dossier, …). All examples below are
real answers from the current build, arrays truncated for readability (never a
structural field removed — the shapes are copied verbatim).
competitor-dossier
Input
{ "mode": "competitor-dossier", "siren": "444608442", "top_n": 20 }
Output (arrays cut; the three modes have genuinely different result shapes)
{"mode": "competitor_dossier","parametres": { "siren": "444608442", "top_n": 20 },"data_version": "2026-07-23","enrichable_siren": "444608442","resultat": {"nb_marches": 130,"montant_total": 3291593920.23,"montant_median": 16272.87,"repartition_cpv": [{ "cpv": "45100000", "nb_marches": 20, "montant_total": 39866.92, "part_marches": 0.153846 },{ "cpv": "09310000", "nb_marches": 13, "montant_total": 1134706.05, "part_marches": 0.1 }// … up to top_n CPV codes],"acheteurs_recurrents": [{ "acheteur_id": "53042890300013", "acheteur_nom": "ETABLISSEMENT PUBLIC FONCIER LOCAL BEARN PYRENEES", "nb_marches": 40, "montant_total": 61268.61, "recurrent": true }// … recurrent = true when nb_marches >= 2],"win_rate": {"valeur": 0.006911,"definition": "weighted mean of award shares per (CPV division x department) segment where the holder is present","n_segments": 60,"n_marches_titulaire_dans_segments": 127,"n_marches_hors_segment": 3,"segments_top": [{ "cpv2": "45", "departement": "64", "n_marches_titulaire": 37, "n_marches_segment": 9838, "part": 0.003761 }// … most-contributing segments]},"tendance_annuelle": [{ "annee": 2024, "nb_marches": 27, "montant_total": 6550723.09 }// … one row per notification year]},"qualite": {"n_marches_total": 130,"n_lignes_etat_courant": 131,"score_qualite_moyen": 0.99,"montant": { "n_retenus": 130, "n_ecartes": 0, "par_raison": {} },"win_rate_marches_hors_segment": 3,"date_notification_exclue": 0,"montant_top1_part": 0.996138},"sources": { "n_uid": 130, "uid": ["120001029000122021100015168000", "130001902003082022110005847000"] }// sources.uid is the COMPLETE, unbounded list of source markets — 130 here — for audit}
market-benchmark
Input
{ "mode": "market-benchmark", "cpv": "71", "departement": "2A", "top_n": 20 }
Optional annee_min / annee_max restrict the window to notification years
(e.g. "annee_min": 2022, "annee_max": 2024); omit both to cover all years.
Output — note this result carries distribution_montants with total and
moyenne, and has no root montant_total:
{"mode": "market_benchmark","parametres": { "cpv_prefix": "71", "zone": "2A", "periode": null, "top_n": 20 },"data_version": "2026-07-23","resultat": {"nb_attributions": 518,"distribution_montants": {"n_retenus": 517, "total": 158956854.39, "moyenne": 307460.07,"q1": 45655.0, "mediane": 97874.0, "q3": 300000.0, "min": 1400.0, "max": 6435734.0},"top_titulaires": [{ "titulaire_siren": "532822202", "titulaire_nom": "CABINET SIBELLA ET ASSOCIES", "nb_marches": 26, "montant_total": 15627803.4 },{ "titulaire_siren": "444523526", "titulaire_nom": "ARTELIA", "nb_marches": 19, "montant_total": 6693070.03 }// … up to top_n],"top_acheteurs": [{ "acheteur_id": "20007695800012", "acheteur_nom": "COLLECTIVITE DE CORSE", "nb_marches": 261, "montant_total": 93828321.74 }// … up to top_n]},"qualite": {"n_marches_total": 518,"n_lignes_etat_courant": 852,"score_qualite_moyen": 0.98,"montant": { "n_retenus": 517, "n_ecartes": 1, "par_raison": { "manquant": 1 } },"zone_regle": "department derived from normalised lieuExecution, postal-code -> department fallback","zone_couverture_globale": { "n_total": 2008019, "n_avec_departement": 1871103, "pct_couverture": 93.18 }},"sources": { "n_uid": 518, "uid": ["058811670000152020002301280000", "060804770000752022xgsg30000000"] }}
buyer-history
Input
{ "mode": "buyer-history", "siret": "21690271800016", "top_n": 20 }
Output — carries a root montant_total and a distribution_montants, but the
distribution here has no total/moyenne:
{"mode": "buyer_history","parametres": { "siret": "21690271800016", "top_n": 20 },"data_version": "2026-07-23","resultat": {"nb_marches": 150,"montant_total": 61096071.18,"distribution_montants": { "n_retenus": 150, "q1": 45000.0, "mediane": 91437.3, "q3": 235000.0, "min": 48.0, "max": 7916968.0 },"cpv_recurrents": [{ "cpv": "03120000", "nb_marches": 10, "montant_total": 507000.0, "part_marches": 0.066667, "recurrent": true }// … up to top_n],"titulaires_favoris": [{ "titulaire_siren": "380129866", "titulaire_nom": "ORANGE (SERVICES DIVERS)", "nb_marches": 6, "montant_total": 808000.0 }// … up to top_n],"saisonnalite_mensuelle": [{ "mois": 5, "nb_marches": 20 }, { "mois": 6, "nb_marches": 20 }// … fixed 12 months],"notifications_datees": 150},"qualite": {"n_marches_total": 150,"n_lignes_etat_courant": 155,"score_qualite_moyen": 0.98,"montant": { "n_retenus": 150, "n_ecartes": 0, "par_raison": {} },"date_notification_exclue": 0,"montant_top1_part": 0.129582},"sources": { "n_uid": 150, "uid": ["2169027180001620", "216902718000162018180400"] }}
Input validation is strict and typed.
siren= 9 digits,siret= 14 digits (leading zeros are real and preserved — SIRETs are strings, never integers),cpv= 2–8 digits,departement= a metropolitan code (01–95except20), Corsica2A/2B, or an overseas code. A valid-but-absent identifier is not an error: it returns a structured empty dossier, never a crash.
Why this is worth paying for (the honest version)
The data is free. France publishes Données Essentielles de la Commande Publique (DECP) as open data. If you download the consolidated file yourself, here is what you actually receive — and why raw numbers off it will mislead you:
- 3,141,176 rows, but only 1,738,976 distinct markets. One market spreads across
many rows: 118,771 markets are co-awarded (several holders) and 307,753 carry
at least one amendment (avenant, up to ~80 versions). Naïvely summing the
montantcolumn double-, triple-, or eighty-folds real spend. This Actor resolves each market to its current version and counts each market once. - Duplicate markets across sources. The file aggregates 61 upstream feeds; 12,449 markets appear in 2 or more of them (up to 7). Deduplication has to happen at the market level — the sources never share a row key.
- ~14,500 malformed supplier SIRETs (wrong length), plus thousands more failing the Luhn checksum. Leading zeros are load-bearing (46,000+ rows would corrupt if read as integers, as the published schema wrongly suggests).
- Sentinel and junk amounts. The 99.9th percentile amount is exactly
999,999,999; the maximum is99,999,999,999.99(1,237 rows ≥ €10 bn — nine-fill placeholders, not real money). There are 2,138 negative amounts and 62,270 zeros. Left in, they wreck every average. - Un-normalised categories. The identifier-type field has 16 spellings where the
spec allows 7;
naturehas 13; the location-type field has 18 —SIRET/Siret/siret,Marché/MARCHE/marché, and so on. Filter on the raw text and you silently drop matches.
What you pay for is the normalisation and the derived metrics — plus a guarantee we never quietly delete a bad record. Every anomaly becomes an auditable flag and a score penalty, never a silent drop. Numbers you can act on, and a paper trail behind each one.
The quality block and the per-record score
Normalisation runs at the row level and attaches, to every record:
quality_flags— the auditable list of everything abnormal about that row (e.g.montant_sentinelle,siret_format_invalide,divergence_intersource,date_notification_aberrante).quality_score=clamp(1 − Σ penalties, 0, 1)— a single number in[0, 1]. Heavier problems cost more: a sentinel or negative amount costs 0.30, a malformed SIRET 0.25, a leading duplicate-source divergence 0.15, a mere casing variant 0.03.
Each query answer then rolls these up in its qualite block:
score_qualite_moyen— average record score over the markets used.montant={ n_retenus, n_ecartes, par_raison }— how many amounts fed the statistics, how many were set aside, and why, broken down by reason (nul,manquant,negatif,sentinelle). An excluded amount is counted and reported, never dropped:n_retenus + n_ecartes = n_marches_total, always.montant_top1_part(competitor & buyer modes) — the share ofmontant_totalheld by the single largest market. A blunt outlier signal with no hidden threshold: ENEDIS reads0.996(one market dominates), the buyer above reads0.130(spread out).zone_regle/zone_couverture_globale(benchmark mode) — how the department was derived and what fraction of the whole dataset carries a derivable department (93.18 %; the rest is reported, never guessed).
Data freshness
- Source: Données essentielles de la commande publique consolidées (format tabulaire) on data.gouv.fr, produced by the Colmo / decp.info pipeline (Colin Maudry). Licence: Licence Ouverte v2.0.
- Refresh: the upstream file is rebuilt roughly daily (early morning UTC); the Actor's cache is refreshed on the same cadence by an internal maintenance run.
- Traceability: every response carries a
data_versiondate, andsources.uidlists every source market — so any answer can be pinned to a build and re-verified against the open data.
FAQ
How reliable are the numbers?
Ten competitor-dossier dossiers were compared against decp.info (the free
reference explorer): 10/10 within a tight tolerance — market counts within ≤ 1.7 %
(≤ 2 markets), amounts 7/7 within ±2 % (4 identical to the cent), top buyer matching
6/7 (the 7th a legitimate tie). A second, independent government source (the DAJ
Opendatasoft dataset) confirmed containment on all 10 (its validated subset ⊆ ours,
no inversions). Honest limit: decp.info runs the same Colmo pipeline as our source,
and DAJ is an upstream subset — so this proves faithful reproduction and consistency,
not independent ground truth. No strictly independent oracle exists for free; we say so
rather than overclaim.
Why are some amounts excluded from the statistics but still shown?
Because dropping them silently would be dishonest. A montant that is null, zero,
negative, or a nine-fill sentinel (999,999,999, ≥ €10 bn) is excluded from totals,
means, medians and quartiles — those figures would otherwise be meaningless — but it is
counted and itemised in qualite.montant.par_raison. You always see how many records
were set aside and for what reason.
What exactly is the "win-rate"? Is it a bid win probability?
No — and we are explicit about it. The DECP only record contracts that were
awarded; lost bids are invisible, so a true tender win-rate is not computable from
this data. Our win_rate is a proxy for market share: the average, over the CPV
division × department segments where the company is active, of its share of awards in
each segment, weighted by how many markets it holds there. Read it as "on the
segments where it operates, what share of awards does it typically win," never as "what
% of tenders it wins." A segments_top list ships alongside so you can see the drivers.
What does montant_top1_part mean?
The fraction of a company's (or buyer's) total amount contributed by its single largest
market. It is a raw outlier flag, with no judgement threshold applied. ENEDIS's 0.996
warns you that €3.28 bn of a €3.29 bn total is one market — the median market is
€16,273. The total is real, but it is not "typical," and this field makes that visible.
Can I get the raw underlying records, not just the aggregates?
The product is designed around one aggregated JSON dossier per query. Line-by-line
delivery of the raw award records is available as a separate, opt-in metered event when
you need the full detail: set "include_records": true in the input (volume capped by
records_limit), and each delivered record is billed as its own event.
Using it from an AI agent
Each mode is a single call returning a single, bounded JSON object — no pagination, no scraping, no session state — which is exactly the shape an LLM tool-call wants. Billing is pay-per-event (you pay per query, described below), which fits agent budgets that meter individual actions. The Actor is built to be callable by autonomous agents and via MCP-style tool use; the specific agentic-payment eligibility flag is being finalised on the platform side and is not asserted here.
Typical agent loop: resolve a company to its SIREN → competitor-dossier →
read enrichable_siren from the response → hand it to a company-enrichment tool (below)
→ combine award history with legal/financial profile.
Related Actors
- dr1ms/french-company-enricher —
every
competitor-dossierresponse exposes a rootenrichable_sirenfield (the subject SIREN). Feed it straight into the French Company Enricher to attach legal status, NAF activity, headcount and financials to the award history you just pulled. Award behaviour on one side, company identity on the other. - Tender Triage — the upstream half of the same workflow. Tender Triage looks at the calls for tenders (the opportunities still open, who might bid); DECP Win Intelligence looks at who actually won once the contracts were awarded. Use them together to go from "what's on the market" to "who takes it."
Pricing
Pay-per-event: you pay per query, nothing for idle time. Each of the three query modes is a metered event; retrieving the raw underlying records (opt-in) is a separate per-record event. There is no subscription and no charge for the daily cache refresh.
Final per-event prices are being set with the maintainer and are not published here yet. There is no SLA and no real-time streaming — the data follows the daily open-data release cadence.
Bookmark this Actor
If French public-procurement intelligence is on your roadmap, bookmark this Actor so you can find it again — and it helps others discover it on the Store. Questions or a segment you want covered? Open an issue on the Actor.