OpenMercantil Webhooks API
Account outbound webhooks: register, update, rotate the HMAC signing secret and delete event subscriptions. Three subscribable event types; deliveries are signed and fail closed on unknown events.
Account outbound webhooks: register, update, rotate the HMAC signing secret and delete event subscriptions. Three subscribable event types; deliveries are signed and fail closed on unknown events.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/openmercantil-webhooks-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
openapi: 3.2.0
info:
title: OpenMercantil Webhooks 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: Webhooks
paths:
/api/v1/user/webhooks:
get:
operationId: listUserWebhooks
tags:
- Webhooks
summary: List outbound webhook metadata
description: Account-plane metadata, the exact payload-contract 1.0 event allowlist and worker health. Full signing secrets are never listed; signing secrets are encrypted at rest with a versioned keyring. Unknown event types and schema versions fail closed.
x-webhook-event-contract-version: '1.0'
x-plane: account
security:
- cookieAuth: []
responses:
'200':
description: Webhook metadata
content:
application/json:
schema:
type: object
properties:
webhooks:
type: array
items:
$ref: '#/components/schemas/OutboundWebhook'
allowed_events:
$ref: '#/components/schemas/OutboundWebhookEventCatalogV1'
contracted_events:
$ref: '#/components/schemas/OutboundWebhookContractedEventCatalogV1'
worker:
$ref: '#/components/schemas/WebhookWorkerHealth'
secret_visible_once:
type: boolean
const: true
'401':
$ref: '#/components/responses/Unauthorized'
'503':
description: Account or webhook security schema unavailable
post:
operationId: createUserWebhook
tags:
- Webhooks
summary: Create an outbound webhook
description: Validates HTTPS destination against SSRF, accepts only the explicit payload-contract 1.0 event allowlist, and atomically stores both the encrypted signing secret and an encrypted 24-hour idempotency replay. An identical Idempotency-Key retry returns the same secret without creating a second webhook. Wildcards and unknown events fail closed with 400.
x-webhook-event-contract-version: '1.0'
x-plane: account
security:
- cookieAuth: []
parameters:
- $ref: '#/components/parameters/CsrfHeader'
- $ref: '#/components/parameters/AccountIdempotencyKeyHeader'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/OutboundWebhookCreateRequest'
responses:
'201':
description: Created, or the exact signing secret replayed for the same key and payload
headers:
Idempotency-Replayed:
$ref: '#/components/headers/IdempotencyReplayed'
Idempotency-Key-Expires:
$ref: '#/components/headers/IdempotencyKeyExpires'
content:
application/json:
schema:
$ref: '#/components/schemas/OutboundWebhookOneTimeResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid CSRF token
'409':
description: Idempotency-Key payload conflict or expired replay window
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Webhook service, secret keyring, or durable idempotency schema/keyring unavailable
/api/v1/user/webhooks/{id}:
patch:
operationId: updateUserWebhook
tags:
- Webhooks
summary: Update an outbound webhook
description: Revalidates URL/DNS/SSRF on every destination change and accepts only payload-contract 1.0 event types. Wildcards and unknown events fail closed with 400. The secret is not returned.
x-webhook-event-contract-version: '1.0'
x-plane: account
security:
- cookieAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
minimum: 1
- name: X-CSRF-Token
in: header
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/OutboundWebhookUpdateRequest'
responses:
'200':
description: Updated metadata
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookUpdatedResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid CSRF token
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Account security or webhook schema unavailable
delete:
operationId: deleteUserWebhook
tags:
- Webhooks
summary: Delete an outbound webhook
x-plane: account
security:
- cookieAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
minimum: 1
- name: X-CSRF-Token
in: header
required: true
schema:
type: string
responses:
'200':
description: Deleted
content:
application/json:
schema:
$ref: '#/components/schemas/DeletedResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid CSRF token
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Account security or webhook schema unavailable
/api/v1/user/webhooks/{id}/rotate:
post:
operationId: rotateUserWebhookSecret
tags:
- Webhooks
summary: Rotate an outbound webhook signing secret
description: Cancels pending deliveries signed with the old key and atomically stores the encrypted replacement response. An identical Idempotency-Key retry within 24 hours returns the same secret without rotating again.
x-plane: account
security:
- cookieAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
minimum: 1
- $ref: '#/components/parameters/CsrfHeader'
- $ref: '#/components/parameters/AccountIdempotencyKeyHeader'
responses:
'200':
description: Rotated, or the exact replacement signing secret replayed for the same key
headers:
Idempotency-Replayed:
$ref: '#/components/headers/IdempotencyReplayed'
Idempotency-Key-Expires:
$ref: '#/components/headers/IdempotencyKeyExpires'
content:
application/json:
schema:
$ref: '#/components/schemas/OutboundWebhookOneTimeResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid CSRF token
'404':
$ref: '#/components/responses/NotFound'
'409':
description: Idempotency-Key payload conflict or expired replay window
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Account security, encryption keyring or webhook schema unavailable
components:
responses:
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'
Unauthorized:
description: Authentication required (no active session)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
BadRequest:
description: Invalid request
headers:
Cache-Control:
$ref: '#/components/headers/NoStoreCacheControl'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: Resource not found
headers:
Cache-Control:
$ref: '#/components/headers/NoStoreCacheControl'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
schemas:
OutboundWebhookEventSubscriptionsV1:
type: array
description: Events with a currently wired durable producer. Contracted-but-unwired event types are not subscribable. An empty input is normalized to alert.triggered; wildcard, inactive and unknown values are rejected.
items:
$ref: '#/components/schemas/OutboundWebhookEventTypeV1'
maxItems: 3
uniqueItems: true
x-webhook-event-contract-version: '1.0'
OutboundWebhookUpdateRequest:
type: object
minProperties: 1
properties:
url:
type: string
format: uri
pattern: ^https://
events:
$ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1'
active:
type: boolean
additionalProperties: false
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
OutboundWebhookContractedEventCatalogV1:
type: array
description: All event payload contracts known by this API version, including contracted-but-unwired events that cannot yet be subscribed.
items:
$ref: '#/components/schemas/OutboundWebhookContractedEventTypeV1'
minItems: 8
maxItems: 8
uniqueItems: true
x-webhook-event-contract-version: '1.0'
OutboundWebhookCreateRequest:
type: object
required:
- url
- events
properties:
url:
type: string
format: uri
pattern: ^https://
events:
$ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1'
additionalProperties: false
OutboundWebhook:
type: object
required:
- id
- url
- events
- active
- secret_prefix
- created_at
properties:
id:
type: integer
minimum: 1
url:
type: string
format: uri
pattern: ^https://
events:
$ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1'
active:
type: boolean
secret_prefix:
type: string
maxLength: 12
description: Non-secret identification prefix.
key_id:
type:
- string
- 'null'
description: Public identifier of the current HMAC signing key.
secret_storage:
type: string
enum:
- encrypted
- migration_required
last_success_at:
type:
- string
- 'null'
format: date-time
created_at:
type: string
format: date-time
additionalProperties: false
OutboundWebhookEventCatalogV1:
description: Exact account-visible list of events with a currently wired durable producer.
allOf:
- $ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1'
- type: array
minItems: 3
maxItems: 3
x-webhook-event-contract-version: '1.0'
DeletedResponse:
type: object
required:
- ok
- deleted
properties:
ok:
type: boolean
deleted:
type: boolean
additionalProperties: false
OutboundWebhookContractedEventTypeV1:
type: string
description: Versioned event names with a defined payload contract. Only names also present in OutboundWebhookEventTypeV1 are currently subscribable.
enum:
- alert.triggered
- borme.daily
- cartera.change
- sanction.matched
- segment.executed
- tender.matched
- tender.deadline
- webhook.test
x-webhook-event-contract-version: '1.0'
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'
OutboundWebhookEventTypeV1:
type: string
description: Exact subscription allowlist for webhook payload contract 1.0. Unknown event types, wildcard subscriptions and unsupported schema versions fail closed. Adding an event requires an explicit runtime, OpenAPI and contract-test change so existing subscriptions never gain future events implicitly.
enum:
- alert.triggered
- tender.matched
- tender.deadline
x-webhook-event-contract-version: '1.0'
x-fail-closed: true
OutboundWebhookOneTimeResponse:
type: object
required:
- ok
- webhook
- secret_visible_once
- idempotency_replayed
- idempotency_expires_at
properties:
ok:
type: boolean
const: true
webhook:
type: object
required:
- id
- secret
- key_id
properties:
id:
type: integer
minimum: 1
secret:
type: string
pattern: ^[a-f0-9]{64}$
readOnly: true
x-sensitive: true
x-visible-once: true
description: Visible only in the original mutation response and identical Idempotency-Key replays during the 24-hour recovery window; never returned by list/get.
key_id:
type: string
events:
$ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1'
additionalProperties: false
secret_visible_once:
type: boolean
const: true
idempotency_replayed:
type: boolean
readOnly: true
idempotency_expires_at:
type: integer
minimum: 1
readOnly: true
description: Unix timestamp for the encrypted replay window.
additionalProperties: false
WebhookUpdatedResponse:
type: object
required:
- ok
- webhook
properties:
ok:
type: boolean
webhook:
$ref: '#/components/schemas/OutboundWebhook'
additionalProperties: false
WebhookWorkerHealth:
type: object
required:
- available
- status
- reason
properties:
available:
type: boolean
status:
type: string
age_seconds:
type:
- integer
- 'null'
reason:
type: string
additionalProperties: false
headers:
IdempotencyKeyExpires:
description: Unix timestamp after which this key cannot replay the one-time secret and a fresh key is required.
schema:
type: integer
minimum: 1
NoStoreCacheControl:
description: Error responses must not be stored.
schema:
type: string
const: no-store
IdempotencyReplayed:
description: True when this response was recovered from the durable encrypted replay record for the same key and payload.
schema:
type: boolean
parameters:
AccountIdempotencyKeyHeader:
name: Idempotency-Key
in: header
required: true
description: Required for account mutations that reveal a secret. Reuse the same key only for an identical payload. The encrypted response can be replayed for 24 hours; a changed payload or an expired key returns 409.
schema:
type: string
minLength: 8
maxLength: 128
pattern: ^[A-Za-z0-9][A-Za-z0-9._:\-]{7,127}$
CsrfHeader:
name: X-CSRF-Token
in: header
required: true
description: Fresh token returned by GET /api/v1/user/me for this authenticated session.
schema:
type: string
minLength: 32
maxLength: 256
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: ob_sess
description: Browser session cookie set after login at /mi-cuenta/login. Mutations also require X-CSRF-Token header (obtain via GET /api/v1/user/me).
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.'
externalDocs:
description: Documentación narrativa con ejemplos en curl/Python/JavaScript
url: https://openmercantil.es/api/documentacion