OpenMercantil Integrations API

Public read-only connector catalog. Never exposes credentials, OAuth tokens, webhook secrets or operator actions.

OpenAPI Specification

openmercantil-integrations-api-openapi.yml Raw ↑
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.'