OpenMercantil Graph API

Corporate and person-to-company relationship graphs. Every emitted record retains the source-specific terms authorized by the active public source catalog; no blanket relicensing applies.

OpenAPI Specification

openmercantil-graph-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: OpenMercantil Graph 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: Graph
  description: Corporate and person-to-company relationship graphs. Every emitted record retains the source-specific
    terms authorized by the active public source catalog; no blanket relicensing applies.
paths:
  /api/v1/grafo/{slug}:
    get:
      security:
      - {}
      - apiKey: []
      - bearerAuth: []
      x-api-credential-scope: companies:read
      operationId: getGrafoBySlug
      x-query-contract:
        allowed:
        - max_children
        unknown: 400 invalid_parameter
        non_scalar: 400 invalid_parameter
        lexical: 400 invalid_parameter
        range: 422 validation_failed
      tags:
      - Graph
      summary: Get corporate graph for a company
      description: Return a graph centered on a company admitted by company_public_v2. Each GLEIF parent/child
        candidate is independently batch-admitted and its public identity is overlaid from the same sidecar;
        absent, personal or ambiguous candidates are omitted. SEC external nodes remain withheld until
        they have an external corporate-identity projection. Source-specific license metadata prevails.
      parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
          example: endesa-energia-sa
        description: Company slug.
      - name: max_children
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 25
        description: Maximum number of children entities returned.
      - $ref: '#/components/parameters/IfNoneMatchHeader'
      responses:
        '200':
          description: Company corporate graph
          headers:
            X-Data-Sources:
              $ref: '#/components/headers/XDataSources'
            X-Source-Catalog-Version:
              $ref: '#/components/headers/XSourceCatalogVersion'
            X-Attribution-Required:
              $ref: '#/components/headers/XAttributionRequired'
            Cache-Control:
              schema:
                type: string
              description: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyGraphResponse'
        '304':
          description: The admitted graph projection has not changed.
        '400':
          $ref: '#/components/responses/BadRequest'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/LegalLayerUnavailable'
  /api/v1/grafo/persona/{slug}:
    get:
      security:
      - {}
      - apiKey: []
      - bearerAuth: []
      x-api-credential-scope: people:read
      operationId: getGrafoPersonaBySlug
      x-query-contract:
        allowed:
        - limit
        unknown: 400 invalid_parameter
        non_scalar: 400 invalid_parameter
        lexical: 400 invalid_parameter
        range: 422 validation_failed
      tags:
      - Graph
      summary: Get person-to-company graph
      description: Return a private/no-store BORME-only graph derived from an exact person_public_v1 report.
        Every company edge is revalidated against the same company_public_v2 generation. It contains no
        UK/external-person enrichment and does not infer identity, control or vigency. Withheld/absent
        is neutral 404; unavailable authority is 503/no-store.
      parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
          example: florentino-perez-rodriguez
        description: Person slug.
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 50
          default: 10
        description: Maximum number of companies returned.
      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'
            Cache-Control:
              schema:
                type: string
                const: private, no-store
          description: Person graph
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PersonGraphResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/LegalLayerUnavailable'
  /api/v1/company/{slug}/network:
    get:
      operationId: getCompanyBySlugNetwork
      tags:
      - Companies
      - Graph
      summary: Documentary network projection (temporarily unavailable)
      description: The former synchronous graph calculation is disabled because its cold path exceeded
        the request budget. This route returns 503 until an indexed, legally governed offline projection
        is available.
      deprecated: true
      parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
      responses:
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          description: Offline projection required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfflineProjectionError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  headers:
    NoStoreCacheControl:
      description: Error responses must not be stored.
      schema:
        type: string
        const: no-store
    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'
    ValidationFailed:
      description: The query is lexically valid but outside a documented numeric or length bound, or uses
        an unsupported indexed combination.
      headers:
        Cache-Control:
          $ref: '#/components/headers/NoStoreCacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    CompanyGraphResponse:
      type: object
      required:
      - schema_version
      - canonical_url
      - center
      - parents
      - children
      - coverage
      - as_of
      properties:
        schema_version:
          type: string
          example: '1'
        canonical_url:
          type: string
          format: uri
        center:
          type: object
          properties:
            slug:
              type: string
            name:
              type: string
            cif:
              type: string
        parents:
          type: array
          items:
            $ref: '#/components/schemas/GraphEdge'
        children:
          type: array
          items:
            $ref: '#/components/schemas/GraphEdge'
        counts:
          type: object
          properties:
            parents:
              type: integer
            children:
              type: integer
        coverage:
          type: object
          additionalProperties:
            type: string
        _source_catalog:
          $ref: '#/components/schemas/SourceCatalogEnvelope'
        as_of:
          type: string
          format: date
          example: '2026-05-18'
        ttl_seconds:
          type: integer
          const: 0
          example: 0
    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
    GraphEdge:
      type: object
      properties:
        slug:
          type:
          - string
          - 'null'
          example: endesa-sa
        name:
          type: string
          example: ENDESA SA
        rel_type:
          type: string
          example: IS_DIRECTLY_CONSOLIDATED_BY
        source_slug:
          type: string
          description: Canonical dataset slug authorized by the active source policy.
    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'
    OfflineProjectionError:
      type: object
      required:
      - error
      properties:
        error:
          type: string
        projection:
          type:
          - string
          - 'null'
        derivation:
          type:
          - string
          - 'null'
        detail:
          type:
          - string
          - 'null'
      additionalProperties: false
    PersonGraphCompany:
      type: object
      required:
      - slug
      - name
      - rel_type
      - role
      - since
      - until
      - documentary_status
      - vigency_verified
      - source_slug
      properties:
        slug:
          type: string
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
        name:
          type: string
          minLength: 1
        rel_type:
          type: string
          const: OFFICER_OF
        role:
          type: string
        since:
          type:
          - string
          - 'null'
          format: date
        until:
          type:
          - string
          - 'null'
          format: date
        documentary_status:
          type: string
          enum:
          - open_documentary_mention
          - historical_documentary_mention
        vigency_verified:
          type: boolean
          const: false
        source_slug:
          type: string
          const: borme
      additionalProperties: false
    PersonGraphResponse:
      type: object
      required:
      - schema_version
      - status
      - canonical_url
      - center
      - companies
      - coverage
      - projection
      - as_of
      - ttl_seconds
      - subject_type
      - identity_resolution
      - _legal_notice
      - _source_catalog
      - _data_sources_used
      properties:
        schema_version:
          type: string
          const: person_graph_v1
        status:
          type: string
          const: available
        canonical_url:
          type: string
          format: uri
        center:
          type: object
          required:
          - slug
          - name
          - companies_count
          - first_seen
          - last_seen
          - subject_type
          - identity_resolution
          properties:
            slug:
              type: string
            name:
              type: string
            companies_count:
              type: integer
              minimum: 1
            first_seen:
              type:
              - string
              - 'null'
              format: date
            last_seen:
              type:
              - string
              - 'null'
              format: date
            subject_type:
              type: string
              const: person_documentary_mentions
            identity_resolution:
              type: string
              const: not_performed
          additionalProperties: false
        companies:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/PersonGraphCompany'
        coverage:
          type: object
          required:
          - borme_officer_edges
          - external_person_enrichment
          properties:
            borme_officer_edges:
              type: string
              const: included
            external_person_enrichment:
              type: string
              const: withheld_not_in_person_public_v1
          additionalProperties: false
        projection:
          $ref: '#/components/schemas/PersonPublicProjectionMetadata'
        as_of:
          type: string
          format: date
        ttl_seconds:
          type: integer
          const: 0
        subject_type:
          type: string
          const: person_documentary_mentions
        identity_resolution:
          type: string
          const: not_performed
        _legal_notice:
          type: string
          minLength: 1
        _source_catalog:
          $ref: '#/components/schemas/SourceCatalogEnvelope'
        _data_sources_used:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/PublicSourcePolicyMetadata'
        _attributions:
          type: object
          additionalProperties:
            type: string
      additionalProperties: false
    PersonPublicProjectionMetadata:
      type: object
      required:
      - name
      - schema_version
      - contract_sha256
      - source_generation
      - content_sha256
      - projected_at
      properties:
        name:
          type: string
          const: person_public_v1
        schema_version:
          type: string
          const: '1.0'
        contract_sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        source_generation:
          type: string
          pattern: ^cpv2-[a-f0-9]{64}$
        content_sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        projected_at:
          type: string
          format: date-time
      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
    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
  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.'