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. 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: 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 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' 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' OutboundWebhookCreateRequest: type: object required: - url - events properties: url: type: string format: uri pattern: ^https:// events: $ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1' additionalProperties: false 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 WebhookUpdatedResponse: type: object required: - ok - webhook properties: ok: type: boolean webhook: $ref: '#/components/schemas/OutboundWebhook' additionalProperties: false OutboundWebhookUpdateRequest: type: object minProperties: 1 properties: url: type: string format: uri pattern: ^https:// events: $ref: '#/components/schemas/OutboundWebhookEventSubscriptionsV1' active: type: boolean 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' 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 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 WebhookWorkerHealth: type: object required: - available - status - reason properties: available: type: boolean status: type: string age_seconds: type: - integer - 'null' reason: type: string 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 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' headers: NoStoreCacheControl: description: Error responses must not be stored. schema: type: string const: no-store 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 IdempotencyReplayed: description: True when this response was recovered from the durable encrypted replay record for the same key and payload. schema: type: boolean responses: BadRequest: description: Invalid request headers: Cache-Control: $ref: '#/components/headers/NoStoreCacheControl' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Unauthorized: description: Authentication required (no active session) 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' 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' 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