# BOAMP Marchés Publics API: Tenders, Awards, Buyers (`actose/boamp-tenders`) Actor

French public tenders from BOAMP: title, buyer, email, deadline, CPV, value, lots, winners, awarded amount, DCE link. Filter by keyword, department, descriptor, buyer, dates. onlyNew daily alerts. No API key, no login. MCP-ready for AI agents. Pay per result. $1 per 1,000 notices

- **URL**: https://apify.com/actose/boamp-tenders.md
- **Developed by:** [ABM](https://apify.com/actose) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 tender notices

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## BOAMP API: French Public Tenders Scraper (Marchés Publics)

Get **French public procurement notices from BOAMP** (Bulletin officiel des annonces des marchés publics) as clean JSON: calls for tenders, award notices with winners and amounts, buyer contacts, CPV codes, deadlines, estimated values, lots, tender documents links. Filter by **keywords, BOAMP descriptors, CPV, department, notice type, buyer, publication date and response deadline**. Turn on **`onlyNew`** for daily tender alerts: you only pay for notices you have not received yet.

**Price: $1.00 per 1,000 notices ($0.001 per notice).** No API key, no login, no monthly rental. Errors, empty results and duplicates are free.

- Source: the official BOAMP open data published by the DILA (French government), about 1.7 million notices, updated every day.
- Covers national notices (MAPA, FNS), European notices published in France (JOUE / eForms) and public service concessions (DSP).
- Every notice is parsed from its full text, whatever its format (eForms, FNSimple or MAPA): you get the same fields every time.
- Ready for AI agents: each notice comes with a `markdown` summary you can send to an LLM or use as a RAG chunk.

### How much does it cost?

You pay **per notice delivered**, nothing else:

| What you get | Price |
|---|---|
| 1 notice | $0.001 |
| 1,000 notices | **$1.00** |
| A daily alert with ~20 new notices, for 30 days | about $0.60 per month |
| A one-off export of every IT contract notice for one year (~3,000) | about $3.00 |

Nothing is charged for errors, for notices dropped by your filters, or for runs that find nothing. If you set a maximum cost per run, the actor stops cleanly when it is reached and never delivers free "preview" items.

### Input

All fields are optional. Filters are combined with AND, and the values inside one filter with OR.

| Field | Description | Example |
|---|---|---|
| `keywords` | Words searched in the notice title (accents and case ignored) | `["informatique", "logiciel"]` |
| `keywordsMatch` | `any` or `all` | `"any"` |
| `keywordsScope` | `title` (recommended) or `fullText` (whole notice) | `"title"` |
| `strictKeywordMatch` | Drops titles that only share a word root ("informations" for "informatique") | `true` |
| `excludeKeywords` | Drop titles containing these words | `["travaux"]` |
| `noticeTypes` | `APPEL_OFFRE` (contract notice), `ATTRIBUTION` (award), `RECTIFICATIF`, `ANNULATION`, `PRE-INFORMATION`, `MODIFICATION`, `EX_ANTE_VOLONTAIRE` | `["APPEL_OFFRE"]` |
| `descriptors` | BOAMP descriptor labels or codes ("Informatique" matches both IT descriptors) | `["Nettoyage de locaux"]` |
| `cpvCodes` | Full 8-digit CPV codes | `["72000000"]` |
| `contractTypes` | `services`, `supplies`, `works` | `["services"]` |
| `departments` | French department codes ("01" and "1" both work, "2A", "974") | `["31", "81"]` |
| `buyerNames` | Words in the buyer name | `["Toulouse Métropole"]` |
| `publishedFrom` / `publishedTo` | YYYY-MM-DD or relative (`"7 days"`) | `"2026-10-01"` |
| `publishedWithinDays` | Shortcut: last N days | `7` |
| `deadlineFrom` / `deadlineTo` | Response deadline range (`"+30 days"` works) | `"+7 days"` |
| `onlyOpen` | Only notices whose deadline is in the future | `true` |
| `onlyNew` | Only notices not delivered by a previous run with the same filters | `true` |
| `maxResults` | Maximum notices (newest first) | `100` |
| `includeNoticeDetails` | Parse the full notice (description, CPV, values, lots, winners, contacts) | `true` |
| `includeRawNotice` | Add the complete original notice as `rawNotice` | `false` |

Example: the 15 latest IT calls for tenders:

```json
{
  "keywords": ["informatique"],
  "noticeTypes": ["APPEL_OFFRE"],
  "maxResults": 15
}
```

### Output

One dataset item per notice. Example (shortened):

```json
{
  "id": "26-100275",
  "url": "https://www.boamp.fr/pages/avis/?q=idweb:26-100275",
  "title": "Prestations de services informatiques",
  "description": "La présente consultation a pour objet la passation de l'accord-cadre visant les prestations de services informatiques...",
  "noticeType": "APPEL_OFFRE",
  "noticeTypeLabel": "Avis de marché",
  "status": "INITIAL",
  "family": "JOUE",
  "familyLabel": "Marchés européens",
  "publicationDate": "2026-10-17",
  "deadline": "2026-11-19T11:00:00+00:00",
  "buyerName": "Saint-Quentin-en-Yvelines - Communauté d'agglomération",
  "buyerAddress": "1 rue Eugène Hénaff",
  "buyerPostalCode": "78192",
  "buyerCity": "Trappes",
  "buyerEmail": "dacp@sqy.fr",
  "buyerPhone": "0139443030",
  "buyerProfileUrl": "http://www.marches-publics.agglo-sqy.fr/",
  "documentsUrl": "https://www.marches-publics.info/mpiaws/index.cfm?fuseaction=dematEnt.login&type=DCE&IDM=1882837",
  "submissionUrl": "https://www.marches-publics.info/mpiaws/index.cfm?fuseaction=demat.termes&IDM=1882837",
  "departments": ["78"],
  "descriptors": ["Informatique (prestations de services)"],
  "descriptorCodes": ["163"],
  "contractTypes": ["SERVICES"],
  "procedureLabel": "Procédure Ouverte",
  "mainCpvCode": "72500000",
  "cpvCodes": ["72500000"],
  "estimatedValue": null,
  "awardedValue": null,
  "currency": null,
  "duration": "48 month",
  "placeOfPerformance": ["FR103"],
  "lots": [{"id": "LOT-0000", "title": null, "description": null, "cpvCode": null, "estimatedValue": null, "currency": null}],
  "lotsCount": 1,
  "winners": [],
  "winnerDetails": [],
  "tendersReceived": null,
  "socialEnvironmentalCriteria": ["environnementaux"],
  "noticeSchema": "eForms",
  "markdown": "## Avis de marché 26-100275: Prestations de services informatiques\n- **Buyer:** Saint-Quentin-en-Yvelines - Communauté d'agglomération\n- **Deadline:** 2026-11-19T11:00:00+00:00\n..."
}
```

Award notices (`ATTRIBUTION`) also include `winners` (names), `winnerDetails` (name, SIRET, city), `awardedValue` and `tendersReceived` when the notice states them. National award notices give the award text in `awardDetails`.

### Use cases

#### 1. Daily tender alert for an IT services company (bid manager)

Every morning at 7:00, a bid manager at an IT services company in Toulouse gets the new IT calls for tenders in Occitanie in Google Sheets, with deadline and DCE link. About 5 to 15 new notices a day, so **under $0.50 a month**.

Input (scheduled daily):

```json
{
  "descriptors": ["Informatique", "Logiciel"],
  "noticeTypes": ["APPEL_OFFRE"],
  "departments": ["31", "32", "81", "82", "09", "11", "12", "46", "65", "66", "34", "30", "48"],
  "onlyOpen": true,
  "onlyNew": true,
  "maxResults": 200
}
```

Output (extract):

```json
[{"id": "26-100036", "title": "INFOGERANCE ET MAINTENANCE INFORMATIQUE DES SITES DU GIP FCIP DE TOULOUSE", "buyerName": "GIP FCIP TOULOUSE", "deadline": "2026-11-23T10:00:00+00:00", "departments": ["31", "12", "46", "81"], "documentsUrl": "https://...", "url": "https://www.boamp.fr/pages/avis/?q=idweb:26-100036"}]
```

Next step: the Google Sheets integration adds a row per notice, and the team qualifies them (go / no go) in a column next to it.

#### 2. Competitor and price watch from award notices (sales director)

A sales director at a cleaning company checks every month which competitors win cleaning contracts in Île-de-France, and at what price. About 300 award notices a month, so **$0.30 a month**.

```json
{
  "descriptors": ["Nettoyage de locaux"],
  "noticeTypes": ["ATTRIBUTION"],
  "departments": ["75", "77", "78", "91", "92", "93", "94", "95"],
  "publishedWithinDays": 31,
  "maxResults": 1000
}
```

Output (extract):

```json
[{"id": "26-9xxxx", "title": "Nettoyage des locaux administratifs", "buyerName": "Ville de ...", "winners": ["Société X"], "winnerDetails": [{"name": "Société X", "siret": "123456789000xx", "city": "Paris", "postalCode": "75011"}], "awardedValue": 412000, "currency": "EUR", "tendersReceived": 6}]
```

Next step: a pivot table by winner gives market shares, contracts won and average contract value; the buyers' names and emails feed the CRM for the next renewal.

#### 3. Lead generation for a public-sector consultancy (business developer)

A consultancy that helps cities with architecture competitions wants every buyer that has launched a project-management (maîtrise d'œuvre) contract worth more than €100k in the last 90 days, with the contact email. About 2,000 notices, so **$2 for the whole list**.

```json
{
  "keywords": ["maîtrise d'oeuvre"],
  "noticeTypes": ["APPEL_OFFRE"],
  "publishedWithinDays": 90,
  "maxResults": 3000
}
```

Output (extract):

```json
[{"buyerName": "Mairie de ...", "buyerCity": "Bastia", "buyerEmail": "marches@...", "buyerPhone": "04...", "estimatedValue": 180000, "title": "Marché de maîtrise d'oeuvre portant sur la réhabilitation ...", "url": "https://www.boamp.fr/pages/avis/?q=idweb:26-99420"}]
```

Next step: filter `estimatedValue > 100000` in the spreadsheet, remove duplicates by `buyerSiret`, then import into the CRM or the emailing tool.

#### 4. AI tender analysis (proposal team)

A proposal team sends the `markdown` field of every new notice to an LLM, which scores how relevant it is to their offer and writes a 3-line summary for Slack. With `onlyNew`, a notice is never analysed (or paid for) twice.

### Call it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<username>/boamp-tenders").call(run_input={
    "keywords": ["informatique"],
    "noticeTypes": ["APPEL_OFFRE"],
    "onlyOpen": True,
    "maxResults": 50,
})
for tender in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(tender["deadline"], tender["buyerName"], tender["title"], tender["url"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<username>/boamp-tenders').call({
    descriptors: ['Informatique'],
    departments: ['31'],
    noticeTypes: ['APPEL_OFFRE'],
    maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((t) => console.log(t.deadline, t.buyerName, t.title));
```

You can also call it over HTTP with `run-sync-get-dataset-items` and get the notices in the response.

### Automation (Make, n8n, Zapier, Google Sheets)

Daily tender alert in **n8n** or **Make**:

1. Create a **schedule** for this actor in Apify Console (e.g. every day at 6:30), with your filters and `"onlyNew": true`.
2. In n8n, add the **Apify trigger** "Actor run succeeded" (or the Make module "Watch Actor Runs").
3. Add **Get dataset items** with the run's `defaultDatasetId`.
4. Send each item to **Google Sheets** (append row: `publicationDate`, `title`, `buyerName`, `deadline`, `estimatedValue`, `url`), **Slack** or **email**.

**Zapier** works the same way with the Apify app ("New finished actor run" trigger). Google Sheets can also be connected directly from the actor's **Integrations** tab.

### Use with AI agents (MCP)

This actor works as a tool in the **Apify MCP server**, so Claude, Cursor, ChatGPT and other AI agents can search French public tenders on demand ("find open cleaning tenders in Lyon closing next month"). The input is simple JSON, and each result has a `markdown` field written for LLM context windows and RAG pipelines.

### FAQ

**Is it legal?** Yes. BOAMP data is official open data published by the French government (DILA) under an open licence. The actor uses the public open-data API. It never logs in and never bypasses any protection.

**How fresh is the data?** BOAMP publishes every day. The open-data API is usually updated the same evening or the next morning.

**What is the difference between `keywords` and `descriptors`?** Keywords search the title. Descriptors are the sector labels given by BOAMP itself (e.g. "Informatique (matériel)", "Espaces verts", "Maîtrise d'oeuvre"). For monitoring, descriptors are usually more complete, and combining both is more precise.

**How does `onlyNew` work?** The actor remembers which notices it has delivered for each set of filters, in a named key-value store (`boamp-tenders-state`) in your Apify account. The next run returns only notices that are not in this memory. It also looks back 7 days, so notices indexed late are not missed. If you change a filter, it counts as a new alert.

**Why is a field empty?** Notices come in several formats, and many buyers leave optional fields blank (estimated value, email, lots). The actor never makes a value up: a missing value is `null`.

**Can I get DECP data (contracts signed below the publication threshold)?** No, this actor covers BOAMP notices only.

**Does it include TED (EU) notices?** It includes every European notice published by French buyers, because they also appear in BOAMP (family `JOUE`).

### Limitations

- Keyword search uses the BOAMP search engine, which matches word roots. With `strictKeywordMatch` (on by default), the actor drops titles that only share the root, but a very short keyword ("bus") can still match longer words that start with it ("business").
- CPV filtering needs full 8-digit codes. A prefix such as "72" is not supported by the source.
- Amounts are only those stated in the notice. Many national notices (MAPA) give no estimated value, and some award notices give the amount only as text (`awardDetails`).
- Older notices (before 2024) use older formats: index fields are always there, but detailed fields can be emptier.
- Departments are those the notice is published for, as BOAMP indexes them. A notice can list several departments.

# Actor input Schema

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

Words searched in the notice title (objet). Accents and case are ignored, French plural/singular forms match. Examples: "informatique", "nettoyage", "maîtrise d'oeuvre", "cybersécurité".

## `keywordsMatch` (type: `string`):

"any": a notice matches if its title contains at least one keyword. "all": it must contain every keyword.

## `keywordsScope` (type: `string`):

"title" (recommended): only the notice title. "fullText": the whole notice (description, lots, conditions...) — many more results, often less relevant.

## `strictKeywordMatch` (type: `boolean`):

The BOAMP search engine matches word roots, so "informatique" also returns titles with "informations". When on, a notice is kept only if each word of the keyword starts a word of its title (accents and case ignored: "informatique" keeps "informatiques", drops "informations"). Dropped notices are not charged. Ignored with full-text search.

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

Drop notices whose title contains one of these words. Example: "travaux", "restauration".

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

BOAMP notice types to return. "Contract notice" = open calls for tenders (avis de marché); "Award notice" = results with winners and amounts (avis d'attribution). Leave empty for all types.

## `descriptors` (type: `array`):

BOAMP descriptor labels or codes. A label matches every descriptor containing it: "Informatique" returns "Informatique (matériel)" and "Informatique (prestations de services)". Other examples: "Logiciel", "Nettoyage de locaux", "Maîtrise d'oeuvre", "Espaces verts", "Formation", or a code like "163".

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

Full 8-digit CPV codes found anywhere in the notice (main or lot CPV). Example: "72000000" (IT services), "90910000" (cleaning), "45000000" (construction works).

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

Services, supplies (fournitures) or works (travaux). Leave empty for all.

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

French department codes where the notice is published, e.g. "31" (Haute-Garonne), "75" (Paris), "01" (Ain), "2A" (Corse-du-Sud), "974" (La Réunion). Leave empty for all of France.

## `buyerNames` (type: `array`):

Words searched in the buyer name. Examples: "Toulouse Métropole", "Région Occitanie", "CHU", "Ministère des Armées".

## `publishedFrom` (type: `string`):

Earliest publication date, inclusive: YYYY-MM-DD or relative such as "7 days" (7 days ago).

## `publishedTo` (type: `string`):

Latest publication date, inclusive: YYYY-MM-DD or relative.

## `publishedWithinDays` (type: `integer`):

Shortcut used when "Published from" is empty: only notices published in the last N days.

## `deadlineFrom` (type: `string`):

Earliest response deadline (date limite de réponse), inclusive: YYYY-MM-DD or relative such as "+7 days". Notices without a deadline (e.g. awards) are excluded when set.

## `deadlineTo` (type: `string`):

Latest response deadline, inclusive: YYYY-MM-DD or relative such as "+30 days".

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

Keep only notices whose response deadline is in the future.

## `onlyNew` (type: `boolean`):

Return only notices not delivered by a previous run with the same filters. The memory is kept in the named key-value store "boamp-tenders-state". Ideal for a daily schedule: you pay only for new notices.

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

Maximum number of notices to return (newest first). Each notice is one charged event.

## `includeNoticeDetails` (type: `boolean`):

Parse the full notice: description, CPV codes, estimated and awarded value, lots, winners, buyer SIRET, email, phone, documents and submission links. Turn off for a lighter export with BOAMP index fields only.

## `includeRawNotice` (type: `boolean`):

Add a "rawNotice" object with the complete original notice (eForms or national schema) for custom parsing.

## Actor input object example

```json
{
  "keywords": [
    "informatique"
  ],
  "keywordsMatch": "any",
  "keywordsScope": "title",
  "strictKeywordMatch": true,
  "noticeTypes": [
    "APPEL_OFFRE"
  ],
  "onlyOpen": false,
  "onlyNew": false,
  "maxResults": 15,
  "includeNoticeDetails": true,
  "includeRawNotice": false
}
```

# Actor output Schema

## `tenders` (type: `string`):

One item per BOAMP notice (overview table).

## `tendersJson` (type: `string`):

Every field of every notice, including lots, winners and the Markdown summary.

# 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 = {
    "keywords": [
        "informatique"
    ],
    "noticeTypes": [
        "APPEL_OFFRE"
    ],
    "maxResults": 15
};

// Run the Actor and wait for it to finish
const run = await client.actor("actose/boamp-tenders").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 = {
    "keywords": ["informatique"],
    "noticeTypes": ["APPEL_OFFRE"],
    "maxResults": 15,
}

# Run the Actor and wait for it to finish
run = client.actor("actose/boamp-tenders").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 '{
  "keywords": [
    "informatique"
  ],
  "noticeTypes": [
    "APPEL_OFFRE"
  ],
  "maxResults": 15
}' |
apify call actose/boamp-tenders --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,actose/boamp-tenders"
        }
    }
}
```

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/aOagyHzN95fEHKhBI/builds/SBBHV7wKyfDFi4rDy/openapi.json
