# France Tenders Monitor: BOAMP, TED & Marchés Publics (`lindenwerk/france-tender-matcher`) Actor

French public tenders and public procurement notices (marchés publics, appels d'offres) from BOAMP and TED in one deduplicated list, incl. MAPA. Tenders scored to your CPV codes, keywords and départements, with deadlines and match reasons. Daily tender alerts (veille).

- **URL**: https://apify.com/lindenwerk/france-tender-matcher.md
- **Developed by:** [Lindenwerk Data](https://apify.com/lindenwerk) (community)
- **Categories:** Business, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 scored match returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What is France Tenders Monitor?

**Find French public tenders (marchés publics, appels d'offres) that actually fit your business.**

- **Sources.** This Actor combines **BOAMP**, the official French bulletin of public procurement notices (European, national and MAPA / adapted-procedure notices), with the EU's **TED (Tenders Electronic Daily)** for French buyers.
- **Matching.** It removes cross-posted duplicates and scores every notice against your profile: CPV codes, French keywords, départements, regions, market level, contract type and value.
- **Output.** You get one clean, flat row per opportunity, with the response deadline (date limite de réponse), a `relevanceScore` from 0 to 100 and plain-language `matchReasons`.

Use it as a **daily tender alert** (monitor mode returns only notices you haven't seen yet), for a **market scan** of
the last 31 days (backfill), or for **competitor tracking** (award notices with winners, awarded values and number of
offers).

### Who it's for

- **Bid managers and sales teams** selling to French public buyers (communes, départements, régions, hospitals,
  universities, State services). They get one scored shortlist each morning instead of checking BOAMP, TED and several buyer profiles.
- **SMEs** looking for smaller contracts. `marketLevels: ["national", "mapa"]` keeps the below-EU-threshold notices
  that TED-only tools never see.
- **Foreign companies entering the French market.** They can use the same agent-friendly schema as our
  [German & EU Tenders Monitor](https://apify.com/lindenwerk/german-eu-tender-matcher), so one profile works for both countries.
- **Tender consultants** monitoring several client profiles (one saved task and `stateKey` per client).
- **Analysts and AI agents** that need clean, structured French procurement data (CPV, NUTS, départements,
  deadlines, values, award winners) through an API.

### What it does

- **Reads BOAMP through its official open-data API** (DILA, Licence Ouverte). It makes one request per publication day and parses all three
  BOAMP formats:
  - European notices in eForms
  - national notices (formulaire national simplifié)
  - MAPA notices
- **Queries the TED Search API v3** for French buyers (country `FRA`).
- **Deduplicates across sources.** A notice published in BOAMP *and* on TED becomes one row with both links, `sources: ["BOAMP", "TED"]`.
  - Matching uses the eForms notice UUID first, then the TED publication number, then a conservative fuzzy match.
  - Identical re-publications are collapsed. The older ids go to `relatedNoticeIds`.
- **Scores relevance with transparent rules** (no LLM, no black box):
  - **CPV codes** by prefix: `72` matches all IT services, `45` all construction works, `909` cleaning.
  - **Keywords**, accent- and case-insensitive (`electricite` = `électricité`), matched in the title and in BOAMP's own
    descriptors (full weight) and in the description (half weight). Rare words count more.
  - Hard filters:
    - `excludeKeywords`
    - `departments` (from BOAMP's département codes or the buyer's postcode, incl. 2A/2B and overseas)
    - `regions` (names such as `Bretagne`, abbreviations such as `IDF`, `PACA`, `AURA`, or NUTS prefixes such as `FRK2`)
    - `marketLevels` (`eu`, `national`, `mapa`)
    - `contractTypes`
    - `minValue` / `maxValue`
    - `onlyOpen` (the deadline is still in the future)
    - `competitorNames` (awards)
- **Three modes:** `monitor`, `backfill` and `awards`.
- **GDPR by default.** Named buyer contacts (`M.` / `Mme` …), personal e-mail addresses (`prenom.nom@…`) and phone numbers are removed. This applies to contact fields and inside free text.
  - Only organisation names and functional contacts (e.g. `marchespublics@ville.fr`, "Direction de la commande publique") are kept.
  - Sole-trader winners of MAPA contracts (`PersonnePhysique`) are never output.

### Why it beats single-source BOAMP scrapers

| | Typical BOAMP scrapers | **This Actor** |
|---|---|---|
| BOAMP national + MAPA notices | ✅ | ✅ |
| French notices that are **only on TED** | ❌ | ✅ |
| Cross-source deduplication | ❌ | ✅ one row per notice, both links kept |
| Relevance scoring with reasons | ❌ or paid AI add-on | ✅ `relevanceScore` + `matchReasons`, included |
| Département *and* region *and* NUTS filters | partly | ✅ |
| "Only new since last run" | some | ✅ persistent state per `stateKey` |
| Award winners from national notices | partly | ✅ legal entities only, sole traders dropped |
| Personal contact data (names, e-mails, phones) | often included | **removed** |
| Logins, proxies, HTML scraping | sometimes | **none**: official open-data APIs only |

We measured a 31-day window (4 Sep to 4 Oct 2026, tender notices):

- The Actor read **7,156 BOAMP** and **4,887 TED** notices for French buyers.
- It merged **3,914 cross-posts**, collapsed 122 re-publications and returned **8,007 unique notices**.
- About **3,200 BOAMP notices** (national and MAPA) were never on TED, so a TED-only alert misses them.
- About **970 TED notices** had no BOAMP counterpart, so a BOAMP-only scraper misses those.

In the 7-day IT example below, 22 of the 70 matches came from TED only.

### How to get daily French tender alerts

1. Click **Start** with the prefilled IT-services example. It's a quick market scan (`backfill`) that returns up to
   25 scored French tenders, so you see real output before you change anything.
2. Replace the CPV codes and keywords with your own. Optionally add départements or regions, market levels, contract types and a value range.
3. Pick a mode: `backfill` for a market scan of the last days, `monitor` for daily alerts that return only notices
   you haven't seen yet, or `awards` for competitor tracking.
4. Look at the results in the **Output** tab (views "Matches" and "Awards") or download them as JSON, CSV or Excel.
5. For daily alerts, switch `mode` to `monitor`, save the input as a **task** and add a **schedule** (for example
   every weekday at 07:00).
6. Send new matches on with Apify **integrations** (Slack, Google Drive, Make, Zapier, n8n or a webhook), or call the Actor from your own code through the **Apify API**.

### Modes

| `mode` | What you get | Typical use |
|---|---|---|
| `monitor` (default) | Notices published in the last `lookbackDays` (default 1) plus today that **earlier runs with the same `stateKey` have not returned**. A first run that finds nothing looks back 7 days once (see below). | Schedule it daily (e.g. 08:00 Europe/Paris) and connect it to e-mail, Slack, a CRM or a spreadsheet. |
| `backfill` | Everything between `dateFrom` and `dateTo` (max 31 days; without dates: the last 7 days). Leaves the monitor state untouched. | Market scan, pipeline building, testing a profile. |
| `awards` | Award notices only (avis d'attribution), with `winnerNames`, `awardedValue` and `numberOfTenders`. | Competitor and price intelligence. |

BOAMP also publishes on Saturdays (we counted 143 notices on Saturday 3 Oct 2026). TED publishes on working days.

#### The Console form vs. API defaults

- **The Console form opens with a short trial:** `mode` = backfill (the last 7 days), the IT profile (CPV 72/48 plus
  `informatique`, `logiciel` and `infogérance`) and `maxResults` = 25. One click shows the 25 best current matches for at
  most USD 0.25. This prefill is also what Apify's daily automated Store test runs. It keeps no state, so it returns
  rows on any day, weekends and holidays included.
- **Before you schedule it**, switch `mode` to **monitor** and raise `maxResults`, or set it to 0 for no limit.
- **API calls, tasks and schedules that leave a field out get the defaults:** `mode` = monitor, `lookbackDays` = 1,
  `maxResults` = 200. The prefill values only fill the form.
- **Warning for one-off API and AI-agent calls: leaving out `mode` runs monitor mode.** That returns only notices from
  the last `lookbackDays` (default 1) that no earlier run with the same `stateKey` has returned, and it remembers them,
  so the same call repeated returns nothing new. For a one-off search, set `"mode": "backfill"` (without
  `dateFrom`/`dateTo` it covers the last 7 days and keeps no state):

  ```json
  {
    "mode": "backfill",
    "cpvCodes": ["72", "48"],
    "keywords": ["cybersécurité", "sécurité informatique", "audit de sécurité"],
    "regions": ["IDF"],
    "maxDaysUntilDeadline": 21,
    "maxResults": 50
  }
  ```
- **First monitor run on a quiet day:** a monitor run with no saved state (the first run for a `stateKey`, or one with
  `resetState`) that finds nothing new looks back `firstRunBackfillDays` (default 7) once. Everything it returns is
  remembered, so the next run is a normal monitor run. Set it to 0 to turn this off.
- **Quiet days and outages:** a monitor run with nothing new still ends *Succeeded*, with 0 rows and a status message.
  Nothing is charged. If BOAMP or TED is down or runs out of time (a failed BOAMP day, a TED error), the run keeps
  everything that source did deliver (finished BOAMP days, TED pages fetched so far) and continues with the other
  source. It never hides this: the status message starts with **PARTIAL DATA**, the log has a `PARTIAL SOURCE` line
  with the missing days, and `RUN_SUMMARY` lists `sourceStatus`, `partialSources`, `missingDays` and `warnings`. You only
  pay for rows that are delivered. If both are unreachable, the run ends *Failed* with a clear message. Nothing is
  charged and the monitor state is left unchanged. A source that hangs is given up after 210 s in short monitor runs;
  backfills get 60 s plus 25 s per day of the window (about 14 minutes for 31 days), so a run never gets stuck.

### Input examples

#### 1. IT services, 7-day scan

```json
{
  "mode": "backfill",
  "profileName": "it-services",
  "cpvCodes": ["72", "48"],
  "keywords": ["logiciel", "informatique", "infogérance", "cybersécurité", "développement"],
  "excludeKeywords": ["fourniture de papier"],
  "dateFrom": "2026-09-28",
  "dateTo": "2026-10-04",
  "minScore": 40
}
```

#### 2. Daily alert: cleaning in Paris and the petite couronne

```json
{
  "mode": "monitor",
  "profileName": "nettoyage-idf",
  "cpvCodes": ["909"],
  "keywords": ["nettoyage", "propreté", "vitrerie"],
  "departments": ["75", "92", "93", "94"],
  "lookbackDays": 1
}
```

#### 3. Construction SME in Auvergne-Rhône-Alpes, smaller contracts only

```json
{
  "mode": "backfill",
  "profileName": "btp-aura-pme",
  "cpvCodes": ["45"],
  "keywords": ["voirie", "réseaux", "VRD", "assainissement"],
  "regions": ["Auvergne-Rhône-Alpes"],
  "marketLevels": ["national", "mapa"],
  "contractTypes": ["works"],
  "minScore": 50
}
```

#### 4. Competitor tracking (awards)

```json
{
  "mode": "awards",
  "competitorNames": ["Eiffage", "Eurovia", "Colas", "Spie"],
  "dateFrom": "2026-09-05",
  "dateTo": "2026-10-04"
}
```

More ready-to-run inputs are in the `examples/` folder of the source code.

#### Input reference (short)

| Field | Meaning |
|---|---|
| `cpvCodes` | CPV prefixes (2–8 digits). `72` = IT services, `48` = software, `45` = construction works, `909` = cleaning, `4873` / `72212730` = security software, `7971` = guarding / security services (gardiennage, not IT). |
| `keywords` / `excludeKeywords` | French (or English) terms. Accents and case are ignored, plurals match. Terms of 3 characters or fewer match whole words only. |
| `departments` | Département codes: `75`, `69`, `6` (= `06`), `2A`, `971`. |
| `regions` | Region names (`Bretagne`, `Île-de-France`, `IDF`, `PACA`, `AURA`, …) or NUTS prefixes (`FR1`, `FRK2`, `FRL04`). |
| `marketLevels` | `eu` (above EU thresholds, also on TED), `national`, `mapa` (adapted procedure, smaller contracts). |
| `contractTypes` | `works`, `services`, `supplies`. |
| `minScore` | 0–100. Only notices at or above it are returned and charged. 40 is a good default; 60 is strict. Keywords raise the score, so a high `minScore` with only a few keywords can return nothing (a CPV-only match scores 50). |
| `onlyOpen` | Drop tenders whose response deadline has passed (default `true`). |
| `minDaysUntilDeadline` / `maxDaysUntilDeadline` | Optional deadline window in days from the run date (the `daysUntilDeadline` field: response deadline, or the participation deadline if no response deadline is published). `"maxDaysUntilDeadline": 21` = due within 3 weeks; `"minDaysUntilDeadline": 5` skips tenders you can't prepare in time. Not set = no filter. When either is set, notices without a published deadline are excluded. Filtered notices are never charged. |
| `sources` | `boamp`, `ted` (default both). |
| `maxResults` | Hard cap on rows (and therefore on cost) per run, best matches first. Default 200 (the Console form prefills 25). |
| `sortBy` | `relevance`, `deadline` or `publicationDate`. |
| `stateKey` / `resetState` | Separate monitor memory per profile, or start fresh. |
| `firstRunBackfillDays` | Monitor only: how far a first run with no saved state looks back if its normal window is empty. Default 7, 0 = off. |

Without `cpvCodes`, `keywords` or `competitorNames`, the Actor returns an **unscored feed** (all notices that pass the
filters), charged at the lower `notice` price.

### Sample output

One row from the IT example (the full sample is in `sample-output.json`):

```json
{
  "noticeId": "dfcd6f13-6392-4aa3-9039-221d19ebb12a",
  "noticeType": "tender",
  "title": "Infogérance et maintenance des serveurs du système d'information de la Ville de Soisy-sous-Montmorency",
  "description": "La Ville de Soisy-sous-Montmorency est dotée de serveurs permettant le fonctionnement de l'ensemble du parc informatique …",
  "buyerName": "COMMUNE DE SOISY-SOUS-MONTMORENCY",
  "buyerCity": "SOISY SOUS MONTMORENCY",
  "buyerPostalCode": "95230",
  "buyerRegion": "Île-de-France",
  "buyerNuts": "FR108",
  "departments": ["95"],
  "buyerEmail": "marchespublics@soisy-sous-montmorency.fr",
  "buyerContactPoint": "Direction de la commande publique",
  "cpvMain": "72514300",
  "cpvCodes": ["72514300", "72600000", "48800000"],
  "contractNature": "services",
  "procedureType": "open",
  "marketLevel": "eu",
  "aboveEuThreshold": true,
  "submissionDeadline": "2026-11-12T12:00:00+01:00",
  "daysUntilDeadline": 38,
  "isOpen": true,
  "publicationDate": "2026-10-02",
  "documentsUrl": "https://commune-soisysousmontmorency.e-marchespublics.com/pack/annonce_marche_public_32691_1176707.html",
  "boampDescriptors": ["Informatique (maintenance serveurs et réseaux)"],
  "boampId": "26-94729",
  "boampUrl": "https://www.boamp.fr/pages/avis/?q=idweb:26-94729",
  "tedUrl": "https://ted.europa.eu/en/notice/-/detail/679248-2026",
  "tedPublicationNumber": "679248-2026",
  "sources": ["BOAMP", "TED"],
  "dedupMethod": "notice-id",
  "relatedNoticeIds": ["boamp:26-82876"],
  "relevanceScore": 85,
  "matchReasons": [
    "Main CPV 72514300 matches profile CPV 72",
    "Keyword 'informatique' in BOAMP descriptor",
    "Keyword 'infogérance' in title",
    "Deadline in 38 day(s)"
  ],
  "profileName": "it-services-idf",
  "retrievedAt": "2026-10-05T02:18:59+02:00"
}
```

Award rows also carry `winnerNames`, `awardedValue`, `awardedCurrency` and `numberOfTenders`. Every run stores a
`RUN_SUMMARY` record in the key-value store: notices per source and day, whether each source is complete
(`sourceStatus`, `missingDays`), dedup statistics, filter counts, matches and bytes downloaded.

#### Output fields

| Group | Fields |
|---|---|
| Identity | `noticeId`, `noticeVersion`, `noticeType` (`tender`, `award`, `planning`, `modification`, `change`, `other`), `formType`, `noticeSubtype`, `relatedNoticeIds` |
| Content | `title`, `description` (shortened), `cpvMain`, `cpvCodes`, `boampDescriptors`, `contractNature`, `procedureType`, `legalBasis`, `marketLevel`, `aboveEuThreshold`, `lotCount` |
| Buyer and place | `buyerName`, `buyerCity`, `buyerPostalCode`, `departments`, `buyerRegion`, `buyerNuts`, `buyerCountry`, `buyerWebsite`, `buyerEmail` and `buyerContactPoint` (functional only), `placeOfPerformanceNuts` |
| Money and dates | `estimatedValue`, `currency`, `publicationDate`, `submissionDeadline`, `participationDeadline`, `daysUntilDeadline`, `isOpen` |
| Links | `boampUrl`, `boampId`, `tedUrl`, `tedPublicationNumber`, `noticeUrl`, `documentsUrl`, `submissionUrl` |
| Matching | `relevanceScore`, `matchReasons`, `profileName`, `sources`, `dedupMethod`, `language`, `retrievedAt` |
| Awards | `winnerNames`, `awardedValue`, `awardedCurrency`, `numberOfTenders` (`awardedValue` is only filled on award and contract-modification notices; it is always null on open tenders, where `estimatedValue` holds the estimate) |

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | USD 0.00005 |
| `scored-match`: a notice that matched your profile at or above `minScore` | **USD 0.01** |
| `notice`: a row in an unscored feed (no CPV, keyword or competitor profile) | **USD 0.003** |

Filtered notices, notices below `minScore`, duplicates and notices already delivered by an earlier monitor run are
**never charged**. Platform compute is included. You can cap cost with `maxResults` and with the run's *maximum total
charge*; the Actor stops cleanly when the limit is reached.

**Real runs on 5 Oct 2026** (inputs in `examples/`):

| Run | Rows returned | Your cost |
|---|---|---|
| Daily `monitor`, cleaning in Paris + 92/93/94 | 3 | **USD 0.03** |
| 7-day `backfill`, IT services, open tenders, minScore 40 | 70 | **USD 0.70** |
| 31-day `backfill`, IT services | 252 | **USD 2.52** |
| 30-day `awards`, four construction groups as competitors | 101 | **USD 1.01** |
| 7-day unscored feed, services in Bouches-du-Rhône, Var, Vaucluse | 98 | 98 × 0.003 = **USD 0.29** |

A daily monitor for a typical profile costs **a few US dollars per month**. You pay only for notices that matched.

### Use with AI agents (MCP)

The input schema is written so that an LLM can call the Actor without guessing: plain-English descriptions, enums,
defaults and examples. Add the Actor to the [Apify MCP server](https://mcp.apify.com) in Claude Desktop, Cursor,
VS Code or any other MCP client:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=actors,lindenwerk/france-tender-matcher"
    }
  }
}
```

Then ask, for example: *"Find open French tenders for cybersecurity audits in Île-de-France and Hauts-de-France from
the last 14 days and list the five closest deadlines."* The agent maps this to:

- `mode: backfill`
- `cpvCodes: ["72"]`
- `keywords: ["cybersécurité", "audit de sécurité", "sécurité informatique"]`
- `regions: ["IDF", "Hauts-de-France"]`
- `sortBy: deadline`

It can then quote `matchReasons` to explain each result. For "due in the next 3 weeks" it adds `maxDaysUntilDeadline: 21`.
Agents should always set `mode` explicitly: without it the call runs as a monitor (see above).

You can also call the Actor through the Apify API
(`POST https://api.apify.com/v2/acts/lindenwerk~france-tender-matcher/run-sync-get-dataset-items`) or the Python/JS
clients. It connects to Make, Zapier, n8n, Google Sheets or Slack through Apify integrations.

### Data sources, licences and attribution

The Actor processes **public, non-personal procurement data** that contracting authorities are legally required to
publish. It uses official open-data interfaces only: no login, no proxies, no HTML scraping.

- **BOAMP**, Direction de l'information légale et administrative (DILA), through the BOAMP open-data API
  (`https://boamp-datadila.opendatasoft.com`, dataset `boamp`). Published under the **Licence Ouverte / Open Licence**
  (Etalab), see https://www.data.gouv.fr/datasets/boamp. **Attribution: "Contient des données BOAMP (DILA), Licence
  Ouverte"**. The data was reshaped and scored by this Actor; the last update date is the `publicationDate` of each notice.
- **TED (Tenders Electronic Daily)**, Publications Office of the European Union, through the Search API v3
  (`https://api.ted.europa.eu/v3/notices/search`). © European Union. Reuse is authorised under **Commission Decision
  2011/833/EU**. Source: TED, https://ted.europa.eu.
- This is an independent tool, not an official service of DILA or the EU. Always check the original notice (links
  included) and the consultation documents before you bid. The official notice prevails.

### GDPR and responsible use

French notices often name a buyer contact (e.g. `correspondantPRM` with civilité, nom, prénom and a personal e-mail).
This Actor **drops personal data by default**:

- It never outputs phone numbers.
- It removes person-like e-mail addresses.
- It replaces `M. / Mme / Monsieur / Madame <nom>` mentions in titles and descriptions with placeholders such as `[nom supprimé]`.
- It keeps only functional contact points.
- MAPA award winners registered as natural persons (sole traders) are dropped. National award winners extracted from text are dropped when they look like a natural person.

Organisation names (buyers and winners) are kept as published. Use the output to find and bid for tenders, not for
contact harvesting or unsolicited marketing.

### Limits

- MAPA notices often have no CPV code. Add keywords to your profile so they can still match (BOAMP descriptors help).
- For national (non-eForms) award notices, winners and amounts come from BOAMP's free text. The extraction is
  conservative, so some winners are missing rather than wrong.
- Region filtering uses NUTS codes and départements. Notices that state neither are dropped when a region filter is set.
- One run covers at most 31 publication days. For longer histories, run several backfills.

### FAQ

**How is `relevanceScore` calculated?**

- CPV evidence (main CPV 0.5, additional CPV 0.35) and keyword evidence (title or descriptor hits count double, rare terms weigh more) are combined as `100 × (1 − (1 − cpv) × (1 − keywords))`.
- In a profile with both CPV codes and keywords, a CPV-only match scores 50, and CPV plus a title keyword scores about 70–85.
- `matchReasons` lists every contribution.

**Why do some rows have no estimated value?** Many buyers don't publish one. Value filters keep notices with unknown
values so you don't miss them.

**Which deadline is `submissionDeadline`?** The date limite de réception des offres, the earliest across lots. It is
converted to an ISO timestamp with the French time zone offset. For restricted procedures, `participationDeadline`
holds the deadline for requests to participate.

**Can I monitor several profiles?** Yes. Create one saved task per profile, each with its own `stateKey`.

**Can I only get tenders due soon (or not too soon)?** Yes. `maxDaysUntilDeadline` keeps notices whose deadline is at
most that many days after the run, `minDaysUntilDeadline` at least that many. Both count from the run date to the
deadline in `daysUntilDeadline`. With either set, notices without a published deadline are dropped. Dropped notices
are not charged.

**What does a run with no matches cost?** Only the Actor start event (USD 0.00005).

**Does it cover other countries?** This Actor is France only. For Germany plus any EU country via TED, see our German & EU Tenders Monitor.

### Examples

- [French IT tenders (BOAMP + TED) - last 7 days](https://apify.com/lindenwerk/france-tender-matcher/examples/french-it-tenders-boamp-ted): a published example task with a ready-made input. Open it, adjust the input and run it in your own Apify account.

### Other Lindenwerk Data Actors

- [German & EU Tenders: Public Procurement Monitor](https://apify.com/lindenwerk/german-eu-tender-matcher): Ausschreibungen from oeffentlichevergabe.de and TED, deduplicated and scored to your CPV codes and keywords.
- [UK Tenders Monitor: Government Contracts & Find a Tender Alerts](https://apify.com/lindenwerk/uk-tender-matcher): Find a Tender notices (above and below threshold) as daily tender alerts, scored to your profile.
- [SAM.gov Government Contract Opportunities Monitor](https://apify.com/lindenwerk/sam-gov-contract-matcher): US federal bids and RFPs from SAM.gov, scored to your NAICS and set-asides.
- [Website Technology & Tech Stack Detector: BuiltWith Alternative](https://apify.com/lindenwerk/tech-stack-detector): CMS, shop system, analytics, consent manager and email provider of any website, in bulk.
- [SEO Audit Crawler: Broken Links, llms.txt & AI Bot Check](https://apify.com/lindenwerk/seo-audit-crawler): on-page SEO, broken links, llms.txt and AI crawler check for whole websites.
- [PDF to Markdown & RAG Chunks: Document Parser (DOCX/PPTX/XLSX)](https://apify.com/lindenwerk/pdf-to-markdown-rag): PDF, Word, PowerPoint and Excel to clean Markdown with inline tables and page-cited RAG chunks; it reads the procurement documents behind each notice's `documentsUrl`.

***

### Deutsch (Kurzfassung)

**Französische öffentliche Ausschreibungen (marchés publics) aus BOAMP und TED in einer Liste: dedupliziert und nach
Ihrem Profil bewertet.**

- **Quellen:** Der Actor lädt die offiziellen Open-Data-Bekanntmachungen des BOAMP (DILA, Licence Ouverte), also EU-weite und nationale Verfahren sowie MAPA-Verfahren unterhalb der EU-Schwellenwerte, und ergänzt sie um französische Bekanntmachungen aus TED.

- **Bewertung:** Doppelte Veröffentlichungen werden zusammengeführt. Jede Bekanntmachung wird nach CPV-Codes, Suchbegriffen (akzentunabhängig), Départements, Regionen, Marktebene und Auftragsart bewertet: `relevanceScore` 0–100 mit Begründung in `matchReasons`.

- **monitor** (Standard bei API-Aufrufen und Zeitplänen): täglich nur neue Ausschreibungen (Ausschreibungs-Alert per Zeitplan)

- **backfill**: Marktüberblick über bis zu 31 Tage. Das Formular in der Console startet damit: die letzten 7 Tage, höchstens 25 Treffer. Vor dem Einplanen auf monitor umstellen. Achtung: Ein API-Aufruf ohne `mode` läuft als monitor; für eine einmalige Suche `"mode": "backfill"` setzen.

- **awards**: Zuschlagsbekanntmachungen mit Gewinnern, Auftragswerten und Anzahl der Angebote

Abrechnung pro Treffer: USD 0,01 je bewertetem Treffer, USD 0,003 je Zeile im ungefilterten Feed. Personenbezogene
Kontaktdaten (Namen, persönliche E-Mail-Adressen, Telefonnummern) werden standardmäßig entfernt. Ideal für deutsche
Unternehmen, die den französischen Markt erschließen. Das Eingabeschema entspricht unserem German & EU Tenders Monitor. Quellen: BOAMP (DILA, Licence Ouverte) und TED, © Europäische Union (Beschluss 2011/833/EU).

### Français (résumé)

**Les marchés publics du BOAMP et de TED, dédoublonnés et notés selon votre profil.**

- Codes CPV, mots-clés sans accents ni majuscules, départements, régions, niveau de marché (européen, national, MAPA), type de marché et montant.
- Chaque avis reçoit un score de 0 à 100 avec ses raisons (`matchReasons`).
- Mode veille quotidienne (`monitor`), analyse sur 31 jours (`backfill`) et suivi des attributions (`awards`).
- Le formulaire s'ouvre sur un essai `backfill` des 7 derniers jours (25 résultats au plus) ; passez en `monitor` avant de planifier une veille. Attention : un appel API sans `mode` s'exécute en `monitor` ; pour une recherche ponctuelle, indiquez `"mode": "backfill"`.
- Filtre optionnel `minDaysUntilDeadline` / `maxDaysUntilDeadline` (jours jusqu'à la date limite, p. ex. `21` = dans les 3 semaines) ; les avis sans date limite sont alors exclus et jamais facturés.
- Les données personnelles des correspondants sont supprimées par défaut.
- Contient des données BOAMP (DILA), Licence Ouverte.

*Made by Lindenwerk Data.*

# Changelog

This Actor's version history is a separate document: https://apify.com/lindenwerk/france-tender-matcher/changelog.md

# Actor input Schema

## `mode` (type: `string`):

monitor = only notices not returned by a previous run with the same stateKey (schedule it daily; this is the default when the field is omitted, e.g. in API calls). backfill = every notice published in dateFrom..dateTo (max 31 days; without dates: the last 7 days). awards = contract award notices only (who won what, at what price) in dateFrom..dateTo. The form opens with backfill so a first try shows the last 7 days right away; switch to monitor before you schedule it.

## `profileName` (type: `string`):

Free label for this search profile, copied to every output row. Also the default stateKey in monitor mode, so different profiles keep separate "already seen" lists. Example: "nettoyage-idf".

## `cpvCodes` (type: `array`):

CPV classification codes or prefixes. A prefix matches every code that starts with it: "72" = IT services, "48" = software, "45" = construction works, "909" = cleaning, "7971" = guarding / security services (gardiennage, not IT), "4873" = security software packages, "72212730" = security software development (for IT security, combine these with keywords), "7132" = engineering design. A main-CPV match gives the strongest score. Check digits like "-8" are ignored. Note: small MAPA notices (< 90 k EUR) usually carry no CPV, so add keywords too.

## `keywords` (type: `array`):

Words matched in the title and BOAMP descriptors (full weight) and in the description (half weight). Case- and accent-insensitive ("electricite" = "électricité"). Keywords with 4+ letters also match inside longer words ("nettoyage" matches "nettoyages"); shorter ones (BTP, VRD) only as whole words. Wrap a keyword in double quotes to force whole-word matching. Notices are in French, so use French terms. Keywords raise relevanceScore, so they interact with minScore: a CPV-only match scores 50, so a high minScore (60+) with only a few keywords can return nothing. Add more keyword variants or lower minScore.

## `excludeKeywords` (type: `array`):

Notices whose title, descriptors or description contain any of these words are dropped (never charged). Same matching rules as keywords. Example: \["fourniture de papier", "restauration"].

## `departments` (type: `array`):

Keep only notices whose buyer or place of performance is in one of these French départements. Codes like "75", "69", "06", "2A", "971" (leading zero optional). Works for every BOAMP notice; TED-only notices are mapped from the buyer postal code.

## `regions` (type: `array`):

Keep only notices located in these regions. Accepts NUTS prefixes ("FR1" Île-de-France, "FRK" Auvergne-Rhône-Alpes, "FRK2" Rhône-Alpes, "FRL04") or region names/abbreviations ("Bretagne", "Île-de-France", "IDF", "PACA", "AURA", "Occitanie"). National and MAPA notices often carry no NUTS code; for them the région is derived from the département, so use region-level prefixes (FR1, FRK ...) or the departments filter for fine-grained filtering.

## `marketLevels` (type: `array`):

Keep only these market levels. Empty = all. SMEs often want \["national", "mapa"] (smaller contracts, less competition).

## `contractTypes` (type: `array`):

Keep only these contract natures. Empty = all.

## `minValue` (type: `integer`):

Drop notices whose published estimated value (awards mode: awarded value) is below this amount. Notices without a published value are kept and say so in matchReasons.

## `maxValue` (type: `integer`):

Drop notices whose published estimated value (awards mode: awarded value) is above this amount.

## `onlyOpen` (type: `boolean`):

Drop tenders whose response deadline (date limite de réponse) has already passed. Tenders without a published deadline are kept.

## `minDaysUntilDeadline` (type: `integer`):

Optional. Keep only notices whose deadline is at least this many days after the run (daysUntilDeadline >= value), e.g. 5 to skip tenders you cannot prepare in time. Days are counted from the run date to the response deadline (the participation-request deadline when no response deadline is published), the same number as the daysUntilDeadline output field. Empty = no filter (default). When set, notices without a published deadline are excluded. Filtered notices are never charged.

## `maxDaysUntilDeadline` (type: `integer`):

Optional. Keep only notices whose deadline is at most this many days after the run (daysUntilDeadline <= value), e.g. 21 = due within three weeks. Days are counted from the run date to the response deadline (the participation-request deadline when no response deadline is published), the same number as the daysUntilDeadline output field. Empty = no filter (default). When set, notices without a published deadline are excluded. Filtered notices are never charged.

## `minScore` (type: `integer`):

Only notices with relevanceScore >= minScore are returned (and charged as scored-match). Guide for a CPV + keyword profile: CPV match only = 50, one title keyword only = 46, CPV + one title keyword = 73, CPV + two keywords = 85+. Keywords raise the score: with few keywords a high minScore can return nothing. Ignored for an unscored feed.

## `competitorNames` (type: `array`):

Only return award notices won by an organisation whose name contains one of these strings (case- and accent-insensitive), e.g. \["Eiffage", "Atos"]. Use with mode = awards.

## `lookbackDays` (type: `integer`):

Monitor mode: how many publication days back to look on each run (plus today). 1 = yesterday and today. Larger values are safe: already delivered notices are skipped and not charged.

## `dateFrom` (type: `string`):

First publication day (YYYY-MM-DD) for backfill and awards mode. Default: dateTo minus 6 days. Ranges longer than 31 days are capped to the last 31 days.

## `dateTo` (type: `string`):

Last publication day (YYYY-MM-DD), inclusive. Default: today.

## `sources` (type: `array`):

boamp = Bulletin officiel des annonces des marchés publics (European, national and MAPA notices). ted = Tenders Electronic Daily, French buyers only (adds EU-level notices published through other French platforms). Use both for full coverage; cross-posted notices are merged.

## `noticeTypes` (type: `array`):

Which notice types to return. Default: tender (monitor/backfill); awards mode forces award.

## `maxResults` (type: `integer`):

Maximum number of rows to return (and charge) in this run, best matches first. 0 = no limit. Default 200; the form prefills 25 for a cheap first try, so raise it (or set 0) for full results. The run also stops when your maximum total charge for the run is reached.

## `sortBy` (type: `string`):

Order of the output rows.

## `includeDescription` (type: `boolean`):

Include a shortened notice description in each row (personal contact details are removed).

## `descriptionMaxLength` (type: `integer`):

Maximum number of characters of the description per row.

## `includeFunctionalContacts` (type: `boolean`):

Include the buyer's functional contact (e.g. marchespublics@ville.fr, "Service de la commande publique"). Personal names, personal e-mail addresses (prenom.nom@...) and phone numbers are never output.

## `stateKey` (type: `string`):

Name of the "already seen" list kept in the named key-value store "fr-tender-matcher-state". Default: the profile name. Use a different key per profile or schedule.

## `resetState` (type: `boolean`):

Forget all notices seen by earlier runs with this stateKey before running (monitor mode).

## `firstRunBackfillDays` (type: `integer`):

Monitor mode: if a run without saved state (the very first run for this stateKey, or after Reset monitor state) finds nothing new in its lookback window, for example because it starts on a weekend or holiday, it looks back this many days once instead, so you see the current open tenders. Later runs are normal monitor runs. 0 = off.

## Actor input object example

```json
{
  "mode": "backfill",
  "cpvCodes": [
    "72",
    "48"
  ],
  "keywords": [
    "informatique",
    "logiciel",
    "infogérance"
  ],
  "onlyOpen": true,
  "minScore": 40,
  "lookbackDays": 1,
  "sources": [
    "boamp",
    "ted"
  ],
  "maxResults": 25,
  "sortBy": "relevance",
  "includeDescription": true,
  "descriptionMaxLength": 500,
  "includeFunctionalContacts": true,
  "resetState": false,
  "firstRunBackfillDays": 7
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one flat row per notice

## `runSummary` (type: `string`):

Counts per source, dedup statistics, filter statistics and runtime

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "backfill",
    "cpvCodes": [
        "72",
        "48"
    ],
    "keywords": [
        "informatique",
        "logiciel",
        "infogérance"
    ],
    "maxResults": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("lindenwerk/france-tender-matcher").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "backfill",
    "cpvCodes": [
        "72",
        "48",
    ],
    "keywords": [
        "informatique",
        "logiciel",
        "infogérance",
    ],
    "maxResults": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("lindenwerk/france-tender-matcher").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "backfill",
  "cpvCodes": [
    "72",
    "48"
  ],
  "keywords": [
    "informatique",
    "logiciel",
    "infogérance"
  ],
  "maxResults": 25
}' |
apify call lindenwerk/france-tender-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lindenwerk/france-tender-matcher"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/w0yhf9PZGn9ggVSOM/builds/QAfaw0AXKlwn2t1qr/openapi.json
