OpenMercantil Integrations API
Public read-only connector catalog. Never exposes credentials, OAuth tokens, webhook secrets or operator actions.
Public read-only connector catalog. Never exposes credentials, OAuth tokens, webhook secrets or operator actions.
openapi: 3.1.0
info:
title: OpenMercantil Integrations 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: Integrations
description: Public read-only connector catalog. Never exposes credentials, OAuth tokens, webhook secrets
or operator actions.
paths:
/api/v1/stripe-webhook:
post:
operationId: receiveStripeWebhook
tags:
- Billing
- Integrations
summary: Receive a signed Stripe event
description: Provider callback, not an end-user API. The exact raw JSON body is capped at 512 KiB
and verified against `Stripe-Signature` before durable inbox claim or side effects. Provider `event.id`
supplies idempotency; terminal duplicates return 200, active leases return 503 for retry, and
failures return 500 so Stripe retries. No webhook secret, signature, raw payload or internal exception
is returned. This route is excluded from every public MCP surface.
x-plane: integration-provider-callback
x-provider: stripe
x-max-body-bytes: 524288
x-idempotency-key: Stripe event.id
x-log-policy: Redact Stripe-Signature and the raw request body
x-mcp-excluded: true
security: []
parameters:
- name: Stripe-Signature
in: header
required: true
description: Stripe-generated signature verified over the exact raw body. It is sensitive request
metadata and must never be logged or echoed.
schema:
type: string
minLength: 1
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/StripeWebhookEventRequest'
responses:
'200':
description: Event accepted, deliberately ignored, or already terminal. All response bodies
are non-secret acknowledgements.
content:
text/plain:
schema:
type: string
enum:
- ok
- duplicate (terminal idempotent skip)
- ignored (other source)
- credits ok
- donation ok
- subscription missing
- historical subscription ignored
- clawback rejected
- clawback
- ignored
- ignored invoice
- ignored payment failure
'400':
description: Signature missing/invalid, or the raw request stream cannot be read
content:
text/plain:
schema:
type: string
const: Bad signature
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error: request_body_unreadable
'413':
description: Raw callback body exceeds 512 KiB
content:
application/json:
schema:
$ref: '#/components/schemas/RequestBodyTooLargeError'
example:
error: request_body_too_large
max_bytes: 524288
'500':
description: Inbox persistence or event processing failed; Stripe must retry
content:
text/plain:
schema:
type: string
enum:
- webhook inbox unavailable
- error
'503':
description: Another worker owns an active lease or a signed OpenMercantil checkout cannot yet
be resolved to its authoritative account owner; Stripe must retry
headers:
Retry-After:
description: Retry delay in seconds
schema:
type: integer
const: 5
content:
text/plain:
schema:
type: string
enum:
- event already processing
- credits owner unavailable
- subscription owner unavailable
/api/v1/integrations:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: integrations:read
operationId: listIntegrations
tags:
- Integrations
summary: List public integration capabilities
description: 'Versioned fail-closed catalog that reports legal egress permission separately from
technical transport availability. public_read is false when permission exists but no safe projection
is active; separate_authority delegates validation to the endpoint''s own fail-closed contract.
Sources in review, restricted or unknown are omitted. The query contract is closed: capability
is optional, but when present it must match the documented lowercase pattern byte-for-byte; empty,
padded or upper-case values and unknown parameters return 400. Provenance contains exactly the
sources represented by the returned items. This is not an operator health probe and never includes
credentials, OAuth tokens, webhook secrets, billing or internal errors.'
parameters:
- name: capability
in: query
schema:
type: string
pattern: ^[a-z][a-z0-9_-]{1,40}$
- $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'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
description: Public integration catalog
content:
application/json:
schema:
$ref: '#/components/schemas/IntegrationListResponse'
'304':
description: The legal catalog and technical transport snapshot 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'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
'400':
$ref: '#/components/responses/BadRequest'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/LegalLayerUnavailable'
/api/v1/integrations/{slug}:
get:
security:
- {}
- apiKey: []
- bearerAuth: []
x-api-credential-scope: integrations:read
operationId: getIntegration
tags:
- Integrations
summary: Get one public integration contract
description: Return the legal-egress decision and the independent technical transport status for
one catalogued integration. This detail route accepts no query parameters; unknown parameters
return 400.
parameters:
- name: slug
in: path
required: true
schema:
type: string
pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
- $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'
ETag:
$ref: '#/components/headers/EntityTag'
Cache-Control:
$ref: '#/components/headers/PrivateRevalidationCacheControl'
description: Public integration metadata
content:
application/json:
schema:
$ref: '#/components/schemas/IntegrationDetailResponse'
'304':
description: The legal decision and technical transport snapshot 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'
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/LegalLayerUnavailable'
components:
headers:
EntityTag:
description: Strong generation-bound entity tag for conditional GET.
schema:
type: string
pattern: ^"[a-f0-9]{64}"$
NoStoreCacheControl:
description: Error responses must not be stored.
schema:
type: string
const: no-store
PrivateRevalidationCacheControl:
description: Caller-specific rate-limit and plan headers make this representation private. A private
cache may retain the body but must revalidate every request; shared caches must not store or reuse
it.
schema:
type: string
const: private, no-cache, must-revalidate
XAttributionRequired:
description: Optional. When present, comma-separated public source aliases whose attribution terms
must accompany reuse.
schema:
type: string
minLength: 1
pattern: ^[a-z0-9][a-z0-9_.-]*(,[a-z0-9][a-z0-9_.-]*)*$
example: placsp
XDataSources:
description: Comma-separated aliases from the active public source catalog that contributed to the
response. Omitted only when a valid exact filter returns an empty representation with zero contributing
sources.
schema:
type: string
minLength: 1
pattern: ^[a-z0-9][a-z0-9_.-]*(,[a-z0-9][a-z0-9_.-]*)*$
example: borme,placsp
XSourceCatalogVersion:
description: Mandatory on every successful public GET. Exact version of the legal source catalog
used to authorize the response.
schema:
type: string
minLength: 1
example: 2026-07-12.2
parameters:
IfNoneMatchHeader:
name: If-None-Match
in: header
required: false
description: Optional RFC 9110 entity-tag validator. Weak validators, comma-separated validator
lists and `*` are accepted.
schema:
type: string
minLength: 1
maxLength: 8192
responses:
BadRequest:
description: Invalid request
headers:
Cache-Control:
$ref: '#/components/headers/NoStoreCacheControl'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
LegalLayerUnavailable:
description: 'Controlled fail-closed denial: the required dataset or legal layer is absent, invalid,
unsupported or not authorized for this public surface.'
headers:
Cache-Control:
$ref: '#/components/headers/NoStoreCacheControl'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error: legal_layer_unavailable
detail: Este dataset no esta habilitado para redistribucion publica por la politica de fuentes
activa.
source_catalog_version: 2026-07-12.2
NotFound:
description: Resource not found
headers:
Cache-Control:
$ref: '#/components/headers/NoStoreCacheControl'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
TooManyRequests:
description: Rate limit exceeded
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
schemas:
ErrorResponse:
type: object
description: Closed compatibility envelope for public/account errors. Route-specific schemas narrow
these fields further where required.
required:
- error
properties:
error:
type: string
minLength: 1
message:
type: string
detail:
type: string
code:
type: string
status:
type:
- integer
- string
projection:
type: string
reason:
type: string
source_catalog_version:
type: string
allowed_parameters:
type: array
uniqueItems: true
items:
type: string
slug:
type: string
key:
type: string
maximum:
type: integer
minimum: 1
parameter:
type: string
fields:
type: array
items:
type: string
max_bytes:
type: integer
minimum: 1
allowed:
type: array
items:
$ref: '#/components/schemas/JsonValue'
valid:
type: array
items:
$ref: '#/components/schemas/JsonValue'
date:
type: string
login_url:
type: string
plan:
type: string
limited_by:
type: string
enum:
- minute
- day
daily_limit:
type: integer
minimum: 1
reset_at:
type: integer
minimum: 1
reset_at_human:
type: string
format: date-time
retry_after_s:
type: integer
minimum: 1
retry_after:
type: integer
minimum: 1
upgrade:
type: string
format: uri
upgrade_url:
type: string
action:
type: string
limit:
type: integer
minimum: 0
remaining:
type: integer
minimum: 0
needed:
type: integer
minimum: 0
shortfall:
type: integer
minimum: 0
ok:
type: boolean
_alias_of:
type: string
additionalProperties: false
IntegrationDetailResponse:
type: object
required:
- schema_version
- source_catalog_version
- integration
- contract
- _source_catalog
- _data_sources_used
properties:
schema_version:
type: string
source_catalog_version:
type: string
integration:
$ref: '#/components/schemas/PublicIntegration'
contract:
$ref: '#/components/schemas/PublicIntegrationContract'
_source_catalog:
$ref: '#/components/schemas/SourceCatalogEnvelope'
_data_sources_used:
type: array
items:
$ref: '#/components/schemas/PublicSourcePolicyMetadata'
_attributions:
type: object
additionalProperties:
type: string
additionalProperties: false
IntegrationListResponse:
type: object
required:
- schema_version
- source_catalog_version
- count
- items
- contract
- _source_catalog
- _data_sources_used
properties:
schema_version:
type: string
source_catalog_version:
type: string
count:
type: integer
items:
type: array
items:
$ref: '#/components/schemas/PublicIntegration'
contract:
$ref: '#/components/schemas/PublicIntegrationContract'
_source_catalog:
$ref: '#/components/schemas/SourceCatalogEnvelope'
_data_sources_used:
type: array
items:
$ref: '#/components/schemas/PublicSourcePolicyMetadata'
_attributions:
type: object
additionalProperties:
type: string
additionalProperties: false
JsonValue:
description: A JSON value used only inside explicitly documented extension maps.
oneOf:
- type:
- string
- number
- boolean
- 'null'
- type: array
items:
$ref: '#/components/schemas/JsonValue'
- type: object
additionalProperties:
$ref: '#/components/schemas/JsonValue'
PublicIntegration:
type: object
required:
- slug
- source_slug
- name
- kind
- capabilities
- public_read
- egress_policy
- transport
- official_url
- license
- attribution_required
- reuse_conditions
- policy_effective_date
- reviewed_at
- catalog_version
properties:
slug:
type: string
pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
source_slug:
type: string
description: Canonical source slug from the active public-source catalog.
name:
type: string
kind:
type: string
capabilities:
type: array
items:
type: string
public_read:
type: boolean
description: True only when transport.status is available or separate_authority; never derived
from legal permission alone.
egress_policy:
$ref: '#/components/schemas/PublicIntegrationEgressPolicy'
transport:
$ref: '#/components/schemas/PublicIntegrationTransport'
official_url:
type: string
format: uri
license:
type: string
license_url:
type:
- string
- 'null'
format: uri
attribution_required:
type: boolean
attribution_text:
type:
- string
- 'null'
reuse_conditions:
type: string
description: Condiciones de reutilizacion aplicables a esta integracion publica.
policy_effective_date:
type: string
format: date
reviewed_at:
type: string
format: date
catalog_version:
type: string
coverage:
$ref: '#/components/schemas/PublicIntegrationCoverage'
additionalProperties: false
PublicIntegrationContract:
type: object
required:
- surface
- secrets_exposed
- writes_exposed
properties:
surface:
type: string
const: public-read-only
secrets_exposed:
type: boolean
const: false
writes_exposed:
type: boolean
const: false
health_semantics:
type:
- string
- 'null'
excluded:
type: array
items:
type: string
additionalProperties: false
PublicIntegrationCoverage:
type: object
properties:
multi_lot:
type:
- string
- 'null'
multiple_cpv:
type:
- string
- 'null'
geography:
type:
- string
- 'null'
additionalProperties: false
PublicIntegrationEgressPolicy:
type: object
description: Legal permission from the active source catalog. It does not claim that a technical
projection is deployed.
required:
- status
- catalog_version
- policy_effective_date
- reviewed_at
properties:
status:
type: string
const: allowed
catalog_version:
type: string
minLength: 1
policy_effective_date:
type: string
format: date
reviewed_at:
type: string
format: date
additionalProperties: false
PublicIntegrationTransport:
type: object
description: Technical read-plane status. unavailable has no endpoint; separate_authority delegates
fail-closed validation to the listed endpoint contract and is not a health probe.
required:
- status
- projection
- generation
- endpoints
properties:
status:
type: string
enum:
- available
- unavailable
- separate_authority
projection:
type:
- string
- 'null'
generation:
type:
- string
- 'null'
endpoints:
type: array
maxItems: 8
items:
type: string
pattern: ^/api/v1/
oneOf:
- properties:
status:
const: available
projection:
type: string
minLength: 1
generation:
type: string
pattern: ^(?:cipv1|sr1)-[a-f0-9]{64}$
endpoints:
minItems: 1
- properties:
status:
const: unavailable
generation:
type: 'null'
endpoints:
maxItems: 0
- properties:
status:
const: separate_authority
projection:
type: string
minLength: 1
generation:
type: 'null'
endpoints:
minItems: 1
additionalProperties: false
PublicSourcePolicyMetadata:
type: object
additionalProperties: false
required:
- slug
- name
- license
- attribution_required
- reuse_conditions
- policy_effective_date
- reviewed_at
- catalog_version
properties:
slug:
type: string
name:
type: string
license:
type: string
license_url:
type:
- string
- 'null'
format: uri
attribution_required:
type: boolean
attribution_text:
type:
- string
- 'null'
official_url:
type:
- string
- 'null'
format: uri
reuse_conditions:
type: string
description: Condiciones de reutilizacion que el consumidor debe conservar al presentar o transformar
el dato.
policy_effective_date:
type: string
format: date
reviewed_at:
type: string
format: date
data_updated_at:
type:
- string
- 'null'
format: date-time
catalog_version:
type: string
RequestBodyTooLargeError:
type: object
required:
- error
- max_bytes
properties:
error:
type: string
const: request_body_too_large
max_bytes:
type: integer
minimum: 1
additionalProperties: false
SourceCatalogEnvelope:
type: object
required:
- catalog_version
- policy_fingerprint
- sources
properties:
catalog_version:
type: string
policy_fingerprint:
type: string
policy_effective_date:
type: string
format: date
sources:
type: object
additionalProperties:
$ref: '#/components/schemas/PublicSourcePolicyMetadata'
additionalProperties: false
StripeWebhookEventRequest:
type: object
description: Stripe Event envelope. The exact signed raw bytes, not a reserialized object, are used
for verification. Provider extension fields remain typed JSON and are not reflected to callers.
required:
- id
- type
- data
properties:
id:
type: string
minLength: 1
description: Provider event id and durable idempotency key.
object:
type: string
const: event
type:
type: string
minLength: 1
created:
type: integer
minimum: 0
livemode:
type: boolean
api_version:
type:
- string
- 'null'
data:
type: object
required:
- object
properties:
object:
type: object
additionalProperties:
$ref: '#/components/schemas/JsonValue'
previous_attributes:
type: object
additionalProperties:
$ref: '#/components/schemas/JsonValue'
additionalProperties:
$ref: '#/components/schemas/JsonValue'
additionalProperties:
$ref: '#/components/schemas/JsonValue'
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
description: Optional opaque omk_* API credential for public GETs. Anonymous access remains valid;
a credential with the operation's x-api-credential-scope (or public:read) selects its account
quota. Never place credentials in query strings.
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque omk_* credential
description: 'Optional Authorization: Bearer transport for the same opaque omk_* API credential
accepted by X-API-Key. It is not a JWT or OAuth access token.'