Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Tenders API
description: Unified French (BOAMP) + EU (TED) public procurement intelligence API. Search tenders, track award outcomes, discover winner patterns.
version: 1.0.0
tags:
- name: tenders
description: Search tender notices. **Available on all tiers** (free, pro).
paths:
/tenders:
get:
tags:
- tenders
summary: Search Tenders
operationId: search_tenders_tenders_get
security:
- APIKeyHeader: []
parameters:
- name: cpv
in: query
required: false
schema:
anyOf:
- type: array
items:
type: string
- type: 'null'
description: 'CPV code(s): repeat or comma-separate'
title: Cpv
description: 'CPV code(s): repeat or comma-separate'
- name: cpv_family
in: query
required: false
schema:
anyOf:
- type: array
items:
type: string
- type: 'null'
description: '2-digit CPV family code(s), e.g. 72 (IT) or 45 (construction). Broader recall: matches BOAMP MAPA notices that have no exact 8-digit CPV.'
title: Cpv Family
description: '2-digit CPV family code(s), e.g. 72 (IT) or 45 (construction). Broader recall: matches BOAMP MAPA notices that have no exact 8-digit CPV.'
- name: descripteur
in: query
required: false
schema:
anyOf:
- type: array
items:
type: string
- type: 'null'
description: BOAMP business descripteur(s), e.g. 'couverture' or 'etude,btp'. Case-insensitive substring match on descripteur_libelle. Targets MAPA notices that lack any CPV.
title: Descripteur
description: BOAMP business descripteur(s), e.g. 'couverture' or 'etude,btp'. Case-insensitive substring match on descripteur_libelle. Targets MAPA notices that lack any CPV.
- name: region
in: query
required: false
schema:
anyOf:
- type: array
items:
type: string
- type: 'null'
description: 'Region slug(s): repeat or comma-separate, e.g. paca,bretagne'
title: Region
description: 'Region slug(s): repeat or comma-separate, e.g. paca,bretagne'
- name: department
in: query
required: false
schema:
anyOf:
- type: array
items:
type: string
- type: 'null'
description: French department code(s), e.g. 60 (Oise) or 60,75. Best coverage on BOAMP source.
title: Department
description: French department code(s), e.g. 60 (Oise) or 60,75. Best coverage on BOAMP source.
- name: country
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: ISO country code, e.g. FR
title: Country
description: ISO country code, e.g. FR
- name: source
in: query
required: false
schema:
anyOf:
- type: array
items:
type: string
- type: 'null'
description: 'Source(s): boamp and/or ted. Combine as source=boamp,ted, or omit for both.'
title: Source
description: 'Source(s): boamp and/or ted. Combine as source=boamp,ted, or omit for both.'
- name: status
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: open (deadline future, or unknown and the BOAMP notice still published or withdrawn less than 60 days ago) | closed (deadline past, or unknown and withdrawn longer ago). 'awarded' currently unsupported (no tender↔award linkage in schema); use /awards endpoint to query attributions directly.
title: Status
description: open (deadline future, or unknown and the BOAMP notice still published or withdrawn less than 60 days ago) | closed (deadline past, or unknown and withdrawn longer ago). 'awarded' currently unsupported (no tender↔award linkage in schema); use /awards endpoint to query attributions directly.
- name: include_planning
in: query
required: false
schema:
type: boolean
description: 'When true, include prior-information / planning notices (PINs) that announce upcoming procurements but are not yet biddable (no submission deadline). Default false: PINs are excluded from /tenders, except those used as a call for competition (pin-cfc-*), which are biddable and always included.'
default: false
title: Include Planning
description: 'When true, include prior-information / planning notices (PINs) that announce upcoming procurements but are not yet biddable (no submission deadline). Default false: PINs are excluded from /tenders, except those used as a call for competition (pin-cfc-*), which are biddable and always included.'
- name: procedure_type
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Procedure Type
- name: contract_type
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Contract Type
- name: buyer_siret
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 14-digit SIRET of the buyer
title: Buyer Siret
description: 14-digit SIRET of the buyer
- name: source_id
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'Publisher''s own notice id, to reconcile an external export with this API (BOAMP ''AA-NNNNNN'' e.g. 26-75464, TED ''NNNNNN-AAAA'' e.g. 000001-2017). Exact match. Returns a list, not a single notice: the id is only unique per source, so combine with source= to disambiguate.'
title: Source Id
description: 'Publisher''s own notice id, to reconcile an external export with this API (BOAMP ''AA-NNNNNN'' e.g. 26-75464, TED ''NNNNNN-AAAA'' e.g. 000001-2017). Exact match. Returns a list, not a single notice: the id is only unique per source, so combine with source= to disambiguate.'
- name: buyer_keyword
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'Partial match on buyer name (case-insensitive, e.g. ''oise'', ''mairie paris''). Alias: buyer_name'
title: Buyer Keyword
description: 'Partial match on buyer name (case-insensitive, e.g. ''oise'', ''mairie paris''). Alias: buyer_name'
- name: keyword
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'Free-text search in title+description (FTS5, accent-insensitive, multi-token AND with prefix matching). Alias: ?q= is accepted (Google/REST convention). For OR search use FTS5 syntax: keyword=BIM OR "maquette numerique" OR IFC. Comma- or pipe-separated lists are NOT parsed as OR. Use the explicit OR keyword. Template literals (Jinja/n8n) must be interpolated client-side before sending. A non-blocking `warning` is returned in the response when a suspicious pattern is detected.'
title: Keyword
description: 'Free-text search in title+description (FTS5, accent-insensitive, multi-token AND with prefix matching). Alias: ?q= is accepted (Google/REST convention). For OR search use FTS5 syntax: keyword=BIM OR "maquette numerique" OR IFC. Comma- or pipe-separated lists are NOT parsed as OR. Use the explicit OR keyword. Template literals (Jinja/n8n) must be interpolated client-side before sending. A non-blocking `warning` is returned in the response when a suspicious pattern is detected.'
- name: budget_min
in: query
required: false
schema:
anyOf:
- type: number
- type: 'null'
description: 'Minimum budget (EUR). Strict by default: tenders with NULL budget are excluded. Set include_null_budget=true to include them. Note: budget data is sparse: only ~0.6% of tenders carry budget info.'
title: Budget Min
description: 'Minimum budget (EUR). Strict by default: tenders with NULL budget are excluded. Set include_null_budget=true to include them. Note: budget data is sparse: only ~0.6% of tenders carry budget info.'
- name: budget_max
in: query
required: false
schema:
anyOf:
- type: number
- type: 'null'
description: Maximum budget (EUR). Same NULL semantics as budget_min.
title: Budget Max
description: Maximum budget (EUR). Same NULL semantics as budget_min.
- name: include_null_budget
in: query
required: false
schema:
type: boolean
description: When true, tenders with NULL budget fields pass through budget_min/budget_max filters. Default false (strict).
default: false
title: Include Null Budget
description: When true, tenders with NULL budget fields pass through budget_min/budget_max filters. Default false (strict).
- name: deadline_after
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'ISO 8601, or alias: today, yesterday, now, Nd/Nh/Nm (e.g. 7d, 24h). Strict by default: tenders with NULL deadline are excluded. Set include_null_deadline=true to keep them. Deadline coverage on TED is partial (~70-88% on recent non-FR notices; some notice types carry no submission deadline).'
title: Deadline After
description: 'ISO 8601, or alias: today, yesterday, now, Nd/Nh/Nm (e.g. 7d, 24h). Strict by default: tenders with NULL deadline are excluded. Set include_null_deadline=true to keep them. Deadline coverage on TED is partial (~70-88% on recent non-FR notices; some notice types carry no submission deadline).'
- name: deadline_before
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: ISO 8601, or alias. Same NULL semantics as deadline_after.
title: Deadline Before
description: ISO 8601, or alias. Same NULL semantics as deadline_after.
- name: include_null_deadline
in: query
required: false
schema:
type: boolean
description: When true, tenders with NULL deadline pass through deadline_after/deadline_before filters. Default false (strict). Useful on TED where many notices have no parsed submission deadline.
default: false
title: Include Null Deadline
description: When true, tenders with NULL deadline pass through deadline_after/deadline_before filters. Default false (strict). Useful on TED where many notices have no parsed submission deadline.
- name: published_after
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'ISO 8601, or alias: today, yesterday, now, Nd/Nh/Nm'
title: Published After
description: 'ISO 8601, or alias: today, yesterday, now, Nd/Nh/Nm'
- name: published_before
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Published Before
- name: sort
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'Result ordering: ''date'' (default: published_at DESC) or ''relevance'' (BM25 full-text ranking, requires keyword=; title matches weigh 5x description matches) or ''deadline'' (soonest submission deadline first, undated notices last; combine with deadline_after=today or status=open to skip expired notices). With sort=relevance each result carries a relevance_score (higher = better match; scores are only comparable within a single query).'
title: Sort
description: 'Result ordering: ''date'' (default: published_at DESC) or ''relevance'' (BM25 full-text ranking, requires keyword=; title matches weigh 5x description matches) or ''deadline'' (soonest submission deadline first, undated notices last; combine with deadline_after=today or status=open to skip expired notices). With sort=relevance each result carries a relevance_score (higher = better match; scores are only comparable within a single query).'
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
title: Page
- name: page_size
in: query
required: false
schema:
type: integer
maximum: 100
minimum: 1
default: 20
title: Page Size
- name: limit
in: query
required: false
schema:
anyOf:
- type: integer
maximum: 100
minimum: 1
- type: 'null'
description: Alias for page_size. If both are passed, limit wins.
title: Limit
description: Alias for page_size. If both are passed, limit wins.
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/TenderListOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/tenders/{tender_id}:
get:
tags:
- tenders
summary: Get Tender
description: 'Fetch one notice by internal id, or by its publisher reference.
Accepts both because callers rarely hold the internal id. An LLM agent
reading a TED notice holds ''510250-2026''; someone reading a BOAMP page
holds ''26-79926''. Until 2026-08-24 both returned a raw pydantic 422
(''Input should be a valid integer''), the only uncurated error left on the
surface, and the one that told the caller the least.
Dispatch is safe on shape alone: every source_id in the corpus carries a
hyphen (TED is NNNNNN-YYYY, BOAMP is NN-NNNNNN), so none of the 2.97M is
purely numeric and none can be mistaken for an internal id.'
operationId: get_tender_tenders__tender_id__get
security:
- APIKeyHeader: []
parameters:
- name: tender_id
in: path
required: true
schema:
type: string
title: Tender Id
- name: source
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'Disambiguate a source_id shared by two sources: ''boamp'' or ''ted''.'
title: Source
description: 'Disambiguate a source_id shared by two sources: ''boamp'' or ''ted''.'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/TenderOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
TenderOut:
properties:
id:
type: integer
title: Id
source:
type: string
title: Source
source_id:
type: string
title: Source Id
source_url:
anyOf:
- type: string
- type: 'null'
title: Source Url
title:
type: string
title: Title
description:
anyOf:
- type: string
- type: 'null'
title: Description
cpv_codes:
items:
type: string
type: array
title: Cpv Codes
cpv_family:
items:
type: string
type: array
title: Cpv Family
descripteur_codes:
items:
type: integer
type: array
title: Descripteur Codes
default: []
descripteur_libelles:
items:
type: string
type: array
title: Descripteur Libelles
default: []
procedure_type:
anyOf:
- type: string
- type: 'null'
title: Procedure Type
contract_type:
anyOf:
- type: string
- type: 'null'
title: Contract Type
buyer_name:
anyOf:
- type: string
- type: 'null'
title: Buyer Name
buyer_siret:
anyOf:
- type: string
- type: 'null'
title: Buyer Siret
buyer_country:
anyOf:
- type: string
- type: 'null'
title: Buyer Country
region:
anyOf:
- type: string
- type: 'null'
title: Region
department:
anyOf:
- type: string
- type: 'null'
title: Department
nuts_code:
anyOf:
- type: string
- type: 'null'
title: Nuts Code
country:
anyOf:
- type: string
- type: 'null'
title: Country
budget_min:
anyOf:
- type: number
- type: 'null'
title: Budget Min
budget_max:
anyOf:
- type: number
- type: 'null'
title: Budget Max
currency:
anyOf:
- type: string
- type: 'null'
title: Currency
published_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Published At
deadline:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Deadline
status:
type: string
title: Status
lot_count:
anyOf:
- type: integer
- type: 'null'
title: Lot Count
updated_at:
type: string
format: date-time
title: Updated At
relevance_score:
anyOf:
- type: number
- type: 'null'
title: Relevance Score
type: object
required:
- id
- source
- source_id
- source_url
- title
- description
- cpv_codes
- cpv_family
- procedure_type
- contract_type
- buyer_name
- buyer_siret
- buyer_country
- region
- department
- nuts_code
- country
- budget_min
- budget_max
- currency
- published_at
- deadline
- status
- lot_count
- updated_at
title: TenderOut
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
type: object
required:
- loc
- msg
- type
title: ValidationError
TenderListOut:
properties:
total:
type: integer
title: Total
page:
type: integer
title: Page
page_size:
type: integer
title: Page Size
results:
items:
$ref: '#/components/schemas/TenderOut'
type: array
title: Results
warning:
anyOf:
- type: string
- type: 'null'
title: Warning
hint:
anyOf:
- type: string
- type: 'null'
title: Hint
client_update:
anyOf:
- type: string
- type: 'null'
title: Client Update
type: object
required:
- total
- page
- page_size
- results
title: TenderListOut
securitySchemes:
APIKeyHeader:
type: apiKey
in: header
name: X-API-Key