OpenMercantil Companies API
Company reports and registry events
Company reports and registry events
openapi: 3.1.0
info:
title: OpenMercantil Companies API
version: 1.9.3
summary: Versioned public-read, browser-account, billing, support and provider-callback contracts.
description: 'Public JSON API for Spanish company information derived from BORME and other public sources.
OpenMercantil is an independent informational service; it is NOT the BOE, BORME or Registro Mercantil
and does NOT replace official certificates or registry extracts.
**Rate limits.** Free: 60 req/min y 200 req/día por IP. Planes superiores (Profesional 5.000 req/día,
MAX 50.000 req/día, Enterprise 500.000+ req/día) según cuenta y API key. Cabeceras `X-RateLimit-Limit`,
`X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-OpenMercantil-Plan`, `Retry-After`.
**License and attribution.** Source-specific metadata in each response and the active versioned source
catalog prevails. OpenMercantil does not relicense upstream content under a blanket license. Unknown,
review and restricted datasets are omitted or return `503 legal_layer_unavailable`. BOE/BORME material
is re-used under Ley 37/2007 and its official version remains boe.es. Court judgments are not exposed;
CENDOJ remains citation-index only under CGPJ Reglamento 3/2010.
**Machine-readable catalog (DCAT-AP-ES):** https://openmercantil.es/catalog.rdf'
termsOfService: https://openmercantil.es/terminos-de-uso
contact:
name: OpenMercantil
url: https://openmercantil.es/soporte
email: social@openmercantil.es
license:
name: Source-specific upstream terms; see response catalog metadata
url: https://openmercantil.es/terminos-de-uso
x-publisher:
name: OpenMercantil
url: https://openmercantil.es/
email: social@openmercantil.es
x-spatial: http://publications.europa.eu/resource/authority/country/ESP
x-temporal: 2009-01-01/..
x-language: es
x-dcat-catalog: https://openmercantil.es/catalog.rdf
x-rate-limit:
free:
per_min: 60
per_day: 200
kind: anonymous-ip
profesional:
per_min: 120
per_day: 5000
kind: api-key
max:
per_min: 600
per_day: 50000
kind: api-key
enterprise:
per_min: 1200
per_day: 500000
kind: contract
x-methodology: https://openmercantil.es/metodologia
x-sources: https://openmercantil.es/fuentes
x-corrections: https://openmercantil.es/correcciones
x-contract-status: Public read, browser-account and provider-callback surfaces are explicitly separated
in this contract. Operator/admin routes are excluded. The public MCP consumes only the allowlisted
GET read plane.
x-account-segment-contract:
projection: company_public_v2 immutable corporate sidecar
synchronous_row_cap: 500
bounded_count_cap: 50001
count_semantics: The segment run response count is the number of rows returned, never a global total.
Dataset preview uses total_is_lower_bound=true and total_lower_bound when the bounded count reaches
50001.
related_web_dataset_surface:
preview_path: /mi-cuenta/datasets/preview
export_path: /mi-cuenta/datasets/export.csv
synchronous_export_max_rows: 500
overflow_status: 503
overflow_error: async_export_required
x-company-identity-contract:
version: '1.0'
projection: company_public_v2 immutable generation-bound corporate sidecar
applies_to: Every /api/v1/company/{slug}*, /api/v1/empresa/{slug}* and /api/v1/grafo/{slug} read before
any report, cache, graph or dataset lookup. /api/v1/companies/compare resolves both requested subjects
in one bounded company_public_v2 batch before either row is exposed; MCP company tools inherit these
preflights through REST.
resolution:
published: canonical corporate slug admitted
safe_alias: internally canonicalized and Content-Location emitted
withheld: neutral 404; includes absent, personal and ambiguous/quarantined identities
unavailable: 503 with no-store; clients must not infer absence
search: Exact corporate CIF, exact canonical/safe-alias slug, or bounded name_prefix2 pool scored
in application code. DNI/NIE and ambiguous CIFs return zero items.
public_company_count: company_public_projection_state.row_count
servers:
- url: https://openmercantil.es
description: Production
tags:
- name: Companies
description: Company reports and registry events
paths:
/api/v1/company/{slug}:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlug
tags:
- Companies
summary: Get a company report
description: Return the structured company report only after company_public_v2 admits a corporate
identity. A safe historical alias is resolved internally and reported through Content-Location;
absent, personal or quarantined identities are neutral 404 responses and projection unavailability
is 503.
parameters:
- name: slug
in: path
required: true
schema:
type: string
example: mercadona-sa
description: Company slug.
- $ref: '#/components/parameters/IfNoneMatchHeader'
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
Content-Location:
$ref: '#/components/headers/CompanyContentLocation'
description: Company report
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyReport'
'304':
description: The admitted, attested company report has not changed.
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/PublicReadUnavailable'
/api/v1/companies/compare:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
x-company-identity-contract:
projection: company_compare_v1
subject_projection: company_public_v2
cardinality: 2
admission: one bounded batch admission for both requested slugs before either company is exposed
canonical_order: lexicographic by canonical slug; requested_slugs, canonical_slugs and companies
remain index-aligned
withheld: neutral 404 with no partial company
unavailable: 503 no-store; never reinterpret as absence
operationId: compareCompanies
x-query-contract:
allowed:
- slugs
unknown: 400 invalid_parameter
non_scalar: 400 invalid_parameter
lexical: 400 invalid_parameter
tags:
- Companies
summary: Compare exactly two admitted companies
description: Returns only the stable company_compare_v1 projection. Both slugs are resolved together
against the immutable company_public_v2 sidecar; safe aliases are canonicalized without redirects.
The response excludes reports, blobs, officers, account data, scores and inferred relationships.
parameters:
- name: slugs
in: query
required: true
style: form
explode: false
description: Exactly two distinct corporate slugs serialized as slugs=empresa-a,empresa-b.
schema:
type: array
minItems: 2
maxItems: 2
uniqueItems: true
items:
type: string
minLength: 1
maxLength: 240
pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
- $ref: '#/components/parameters/IfNoneMatchHeader'
responses:
'200':
description: Two admitted companies in stable canonical order
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
Content-Location:
$ref: '#/components/headers/CompanyCompareContentLocation'
ETag:
$ref: '#/components/headers/EntityTag'
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyCompareResponse'
'304':
description: The generation-bound comparison has not changed
headers:
Content-Location:
$ref: '#/components/headers/CompanyCompareContentLocation'
ETag:
$ref: '#/components/headers/EntityTag'
'400':
description: Malformed pair or both inputs resolve to the same company
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyCompareInvalidRequest'
'404':
description: At least one subject is absent, personal, ambiguous, quarantined or legally withheld
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyCompareNotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: The immutable company comparison projection or its authority is unavailable
headers:
Cache-Control:
description: Projection outages are never cacheable.
schema:
type: string
const: no-store
Retry-After:
description: Seconds to wait before a bounded retry.
schema:
type: integer
minimum: 1
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/ProjectionUnavailableError'
- $ref: '#/components/schemas/CompanyCompareLegalUnavailable'
/api/v1/datasets/public:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: listPublicCompanyDownloads
parameters:
- $ref: '#/components/parameters/IfNoneMatchHeader'
tags:
- Datasets
- Companies
summary: List generation-bound public company downloads
description: Lists exactly the three bounded public company downloads admitted by company_public
artifact contract 1.1. Every member is reverified through the immutable manifest and SHA-256 ledger
before this atomic catalog is emitted. A missing, stale or tampered member makes the whole projection
unavailable; raw company tables, local paths and legacy docroot fallbacks are never exposed.
responses:
'200':
description: Atomic catalog of the three admitted public company downloads
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
ETag:
$ref: '#/components/headers/EntityTag'
content:
application/json:
schema:
$ref: '#/components/schemas/PublicCompanyDownloadCatalog'
'304':
description: The generation-bound download catalog has not changed
headers:
ETag:
$ref: '#/components/headers/EntityTag'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/PublicReadUnavailable'
/api/v1/company/{slug}/events:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlugEvents
tags:
- Companies
- BORME
summary: Get paginated company events
description: Return BORME events for a known company slug, optionally filtered by calendar year.
The closed scalar query contract rejects unknown names, arrays and malformed integer values with
400; valid integers outside their bounds return 422. If year is omitted, the server freezes the
current UTC year for the request. Historical coverage starts at 1900 and year must be less than
or equal to the current UTC year; there is no fixed calendar ceiling.
x-query-contract:
allowed:
- year
- page
- page_size
unknown: 400 invalid_parameter
non_scalar: 400 invalid_parameter
lexical: 400 invalid_parameter
range: 422 validation_failed
parameters:
- name: slug
in: path
required: true
schema:
type: string
example: mercadona-sa
- name: year
in: query
required: false
description: Calendar year from 1900 through the current UTC year (year <= current UTC year).
Defaults dynamically to the current UTC year.
schema:
type: integer
minimum: 1900
x-maximum-dynamic: current_utc_year
example: 2026
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 1000
default: 1
- name: page_size
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 50
- $ref: '#/components/parameters/IfNoneMatchHeader'
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
description: Paginated event list
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyEventsResponse'
'304':
description: The generation-bound event page has not changed.
'404':
$ref: '#/components/responses/NotFound'
'400':
$ref: '#/components/responses/BadRequest'
'422':
description: A validly typed year, page or page_size is outside its documented range
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalServerError'
'503':
$ref: '#/components/responses/PublicReadUnavailable'
/api/v1/company/{slug}/timeline:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlugTimeline
tags:
- Companies
- BORME
summary: Get unified multi-source company timeline
description: Fail-closed derived route. Return chronological events from the exact hash-attested
BORME company report and the generation/company-bound PLACSP projection. BDNS coverage is explicitly
unavailable_generation_bound_projection_missing and no BDNS row, empty-list claim or fallback
is emitted. If either available authority cannot be verified, the route returns 503 without reading
or caching raw source data.
parameters:
- name: slug
in: path
required: true
schema:
type: string
example: banco-santander-s-a
description: Company slug.
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
description: Multi-source timeline
content:
application/json:
schema:
type: object
required:
- slug
- count
- sources
- events
- coverage
properties:
slug:
type: string
count:
type: integer
maximum: 100
sources:
type: array
items:
type: string
enum:
- borme
- placsp
minItems: 2
maxItems: 2
example:
- borme
- placsp
events:
type: array
items:
type: object
properties:
date:
type: string
format: date
category:
type: string
enum:
- borme
- contratos
type:
type: string
title:
type: string
amount_eur:
type:
- number
- 'null'
source_slug:
type: string
enum:
- borme
- placsp
source_url:
type: string
confidence:
type: string
enum:
- high
- medium
- low
amount_semantics:
type: string
additionalProperties: false
coverage:
type: object
required:
- borme
- placsp
- bdns
properties:
borme:
type: string
placsp:
type: string
bdns:
type: string
const: unavailable_generation_bound_projection_missing
additionalProperties: false
_disclaimer:
type: string
'404':
$ref: '#/components/responses/NotFound'
'503':
$ref: '#/components/responses/LegalLayerUnavailable'
'429':
$ref: '#/components/responses/TooManyRequests'
/api/v1/company/{slug}/officers:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlugOfficers
tags:
- Companies
- Persons
summary: Get current and historical company officers
description: Return at most 500 officer mentions from the same cached, policy-filtered company projection
used by the canonical company route. The JSON body is capped at 1 MiB. Persons are documentary
mentions only — no DNI, contact data or personal address.
x-rate-limit: plan policy (see info.x-rate-limit)
parameters:
- name: slug
in: path
required: true
schema:
type: string
example: banco-santander-s-a
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
description: Officer list
content:
application/json:
schema:
$ref: '#/components/schemas/OfficerList'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Bounded cached officer projection unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/OfflineProjectionError'
/api/v1/company/{slug}/contracts:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlugContracts
x-query-contract:
allowed:
- limit
unknown: 400 invalid_parameter
non_scalar: 400 invalid_parameter
lexical: 400 invalid_parameter
range: 422 validation_failed
tags:
- Companies
- Public Procurement
summary: Get PLACSP procurement notices linked to a legal entity
description: Requires the legal-first PLACSP v3 projection, including typed budget, estimate and
award fields plus corporate supplier edges. It never falls back to the legacy untyped table and
never sums mixed monetary concepts. A CIF link is documentary and does not prove payment or executed
spend.
x-rate-limit: plan policy (see info.x-rate-limit)
parameters:
- name: slug
in: path
required: true
schema:
type: string
example: banco-santander-s-a
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 30
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
description: Contracts list
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyProcurementResponse'
'400':
$ref: '#/components/responses/BadRequest'
'422':
$ref: '#/components/responses/ValidationFailed'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalServerError'
'503':
$ref: '#/components/responses/PublicReadUnavailable'
/api/v1/company/{slug}/procurement:
get:
operationId: getCompanyBySlugProcurement
x-query-contract:
allowed: []
unknown: 400 invalid_parameter
non_scalar: 400 invalid_parameter
lexical: 400 invalid_parameter
tags:
- Companies
- Public Procurement
summary: Alias of /contracts
description: Documentary alias for /contracts endpoint. Same payload and projection requirements;
`_alias_of` contains the canonical route.
x-rate-limit: plan policy (see info.x-rate-limit)
parameters:
- name: slug
in: path
required: true
schema:
type: string
responses:
'200':
description: Procurement awards (alias of contracts)
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyProcurementResponse'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalServerError'
'503':
description: Legal-first PLACSP projection unavailable
/api/v1/company/{slug}/grants:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlugGrants
x-query-contract:
allowed:
- limit
unknown: 400 invalid_parameter
non_scalar: 400 invalid_parameter
lexical: 400 invalid_parameter
range: 422 validation_failed
tags:
- Companies
- Public Procurement
summary: Get BDNS public grants
description: Read only company_integrations_public_v1 after company_public_v2 admission. Returns
up to 200 attested BDNS rows and never queries the canonical grants table. Monetary values are
awarded amounts in integer cents, never payments; ambiguous raw zero is null. Empty is complete
bounded-projection coverage, not proof of absence upstream.
parameters:
- $ref: '#/components/parameters/CompanySlugPath'
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 100
- $ref: '#/components/parameters/IfNoneMatchHeader'
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
Content-Location:
$ref: '#/components/headers/CompanyContentLocation'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
description: Grants list
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyGrantsResponse'
'304':
description: The admitted company, projection generation and legal envelope have not changed
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
Content-Location:
$ref: '#/components/headers/CompanyContentLocation'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
'400':
$ref: '#/components/responses/BadRequest'
'422':
$ref: '#/components/responses/ValidationFailed'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/PublicReadUnavailable'
/api/v1/company/{slug}/ip:
get:
operationId: getCompanyBySlugIp
tags:
- Companies
summary: Get policy-gated aggregated trademarks and patents
description: Fail-closed aggregate over OEPM/EUIPO/EPO data. Because upstream provenance cannot
currently be separated in the aggregate, the route returns 503 until an exact aggregate-dataset
decision authorizes every component and derivative use.
parameters:
- name: slug
in: path
required: true
schema:
type: string
responses:
'200':
description: Trademarks + patents counts with states legend
content:
application/json:
schema:
type: object
properties:
slug:
type: string
trademarks:
type: object
properties:
count:
type: integer
active:
type: integer
sources:
type: array
items:
type: string
example:
- OEPM
- EUIPO
patents:
type: object
properties:
count:
type: integer
active:
type: integer
sources:
type: array
items:
type: string
example:
- OEPM
- EPO
- WIPO
states_legend:
type: object
additionalProperties:
type: string
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/LegalLayerUnavailable'
/api/v1/company/{slug}/sources:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: companies:read
operationId: getCompanyBySlugSources
x-query-contract:
allowed: []
unknown: 400 invalid_parameter
non_scalar: 400 invalid_parameter
lexical: 400 invalid_parameter
tags:
- Companies
summary: Get bounded public integration coverage for a company
description: Return the four generation-bound company integration sources (BDNS, CNMV, TED and Wikidata)
with explicit available/empty coverage and projection-bound license metadata. It never probes
BORME, PLACSP, OEPM, GLEIF, sanctions, raw helpers or connector telemetry. Empty never proves
upstream absence.
parameters:
- $ref: '#/components/parameters/CompanySlugPath'
- $ref: '#/components/parameters/IfNoneMatchHeader'
responses:
'200':
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
Content-Location:
$ref: '#/components/headers/CompanyContentLocation'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
description: Generation-bound coverage for exactly four safe company integrations
content:
application/json:
schema:
$ref: '#/components/schemas/CompanySourcesResponse'
'304':
description: The admitted company, projection generation and legal envelope have not changed
headers:
X-Data-Sources:
$ref: '#/components/headers/XDataSources'
X-Source-Catalog-Version:
$ref: '#/components/headers/XSourceCatalogVersion'
X-Attribution-Required:
$ref: '#/components/headers/XAttributionRequired'
Content-Location:
$ref: '#/components/headers/CompanyContentLocation'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/PublicReadUnavailable'
/api/v1/company/{slug}/sanctions:
get:
operationId: getCompanyBySlugSanctions
tags:
- Companies
- Risk Signals
summary: Get policy-gated sanctions dataset
description: This route is fail-closed. It returns data only when the exact sanctions dataset has
# --- truncated at 32 KB (144 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/openmercantil/refs/heads/main/openapi/openmercantil-companies-api-openapi.yml