openapi: 3.1.0
info:
  title: skanfirmy.pl — Polish company verification API
  version: "1.0.0"
  description: |
    Free, keyless REST API for verifying Polish companies straight from official
    government registers. No API key, no registration, no limits beyond the source
    registers' own.

    Every endpoint returns human-friendly HTML by default and machine JSON when you
    ask for it, either with `?format=json` or an `Accept: application/json` header.

    Values that come verbatim from a government register (e.g. the VAT status
    literal `Czynny` / `Zwolniony`) are kept unchanged, and language-neutral derived
    fields are added alongside (e.g. `vatActive`, `statusVatCode`) so agents can
    branch on a stable value without parsing Polish.

    Sources: Ministry of Finance VAT White List ("Biała Lista"), National Court
    Register (KRS, Ministry of Justice), REGON (GUS), and the EU VIES system.

    There is also a Model Context Protocol server at `POST /mcp` (JSON-RPC 2.0) for
    AI agents; it is not described in this OpenAPI document.
  contact:
    name: skanfirmy.pl
    url: https://skanfirmy.pl/
  license:
    name: Data from public government registers
    url: https://skanfirmy.pl/regulamin
servers:
  - url: https://skanfirmy.pl
tags:
  - name: company
    description: Company lookups by identifier
  - name: eu-vat
    description: EU VAT number validation

paths:
  /nip/{number}:
    get:
      tags: [company]
      operationId: getCompanyByNip
      summary: Aggregated company record by NIP
      description: |
        Returns a full record for a Polish company by its 10-digit NIP: VAT status
        from the White List plus, when the entity is in the KRS, its legal data
        (legal form, share capital, representation, PKD codes) in a single call.
      parameters:
        - $ref: "#/components/parameters/Nip"
        - $ref: "#/components/parameters/Format"
      responses:
        "200":
          description: Company found.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NipResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/UpstreamError" }
        "503": { $ref: "#/components/responses/UpstreamUnavailable" }

  /nips/{lista}:
    get:
      tags: [company]
      operationId: getCompaniesByNipBatch
      summary: Batch VAT lookup for up to 30 NIPs
      description: |
        Comma-separated list of up to 30 NIPs. Returns VAT status and basic data for
        each, with the same raw+derived fields as `/nip`.
      parameters:
        - name: lista
          in: path
          required: true
          description: Comma-separated NIPs (max 30), e.g. `5260250274,7740001454`.
          schema: { type: string }
          example: "5260250274,7740001454"
        - $ref: "#/components/parameters/Format"
      responses:
        "200":
          description: Batch processed.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NipsResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "502": { $ref: "#/components/responses/UpstreamError" }

  /regon/{nip}:
    get:
      tags: [company]
      operationId: getRegonByNip
      summary: REGON (GUS) registry data by NIP
      description: |
        Official REGON data from the GUS BIR register by NIP — including sole
        traders (JDG), which are not in the KRS.
      parameters:
        - $ref: "#/components/parameters/Nip"
        - $ref: "#/components/parameters/Format"
      responses:
        "200":
          description: Entity found in REGON.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegonResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "502": { $ref: "#/components/responses/UpstreamError" }

  /krs/{number}:
    get:
      tags: [company]
      operationId: getKrsByNumber
      summary: KRS extract by KRS number
      description: Current KRS extract (from the Ministry of Justice open KRS API) by 10-digit KRS number. Always returns JSON (no HTML variant).
      parameters:
        - name: number
          in: path
          required: true
          description: 10-digit KRS number (leading zeros allowed, e.g. `0000028860`).
          schema: { type: string, pattern: "^[0-9]{1,10}$" }
          example: "0000028860"
      responses:
        "200":
          description: KRS entry found.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/KrsResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "502": { $ref: "#/components/responses/UpstreamError" }

  /firma-po-regon/{regon}:
    get:
      tags: [company]
      operationId: getCompanyByRegon
      summary: Company identity by REGON number
      description: |
        Resolve a company by its REGON number (9 or 14 digits) via the GUS BIR
        register — returns the official name, NIP, legal form and address. The
        reverse of /regon/{nip}: given only a REGON you recover the NIP and identity.
      parameters:
        - name: regon
          in: path
          required: true
          description: 9- or 14-digit REGON number.
          schema: { type: string, pattern: "^[0-9]{9}([0-9]{5})?$" }
          example: "610188201"
        - $ref: "#/components/parameters/Format"
      responses:
        "200":
          description: Entity found in REGON.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegonResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "502": { $ref: "#/components/responses/UpstreamError" }

  /vies/{country}/{number}:
    get:
      tags: [eu-vat]
      operationId: validateEuVat
      summary: Validate an EU VAT number (VIES)
      description: |
        Validates a counterparty's EU VAT number via the European Commission VIES
        service. `result` distinguishes a valid number, a well-formed but
        unregistered one, a malformed input, and a VIES outage — so an agent never
        mistakes downtime for an invalid VAT ID.
      parameters:
        - name: country
          in: path
          required: true
          description: Two-letter EU country code (e.g. `PL`, `DE`, `FR`; `XI` for Northern Ireland).
          schema: { type: string, minLength: 2, maxLength: 2 }
          example: "PL"
        - name: number
          in: path
          required: true
          description: VAT number without the country prefix.
          schema: { type: string }
          example: "5260250274"
        - $ref: "#/components/parameters/Format"
      responses:
        "200":
          description: VIES answered (see `result` for the outcome).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ViesResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "503": { $ref: "#/components/responses/UpstreamUnavailable" }

components:
  parameters:
    Nip:
      name: number
      in: path
      required: true
      description: 10-digit Polish tax identifier (NIP), digits only.
      schema: { type: string, pattern: "^[0-9]{10}$" }
      example: "5260250274"
    Format:
      name: format
      in: query
      required: false
      description: "Set to `json` for a JSON response (equivalent to sending an `Accept: application/json` header). Omitted returns HTML."
      schema: { type: string, enum: [json] }

  responses:
    BadRequest:
      description: Invalid identifier (wrong length or checksum).
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: No entity found for this identifier.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: The source register's daily quota was reached; try again later.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    UpstreamError:
      description: The source register returned an error.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    UpstreamUnavailable:
      description: The source register is temporarily unavailable.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:
    Error:
      type: object
      properties:
        error: { type: string, description: Human-readable error message (Polish). }
      required: [error]

    VatFields:
      type: object
      description: Raw government literal plus language-neutral derived fields.
      properties:
        statusVat: { type: string, description: "Raw MF literal, verbatim.", examples: ["Czynny", "Zwolniony"] }
        vatActive: { type: boolean, description: "Derived: true only for an active VAT payer (statusVat == 'Czynny')." }
        statusVatCode:
          type: string
          description: "Derived, language-neutral enum."
          enum: [active, exempt, not_registered]

    PkdItem:
      type: object
      properties:
        code: { type: string, example: "19.20.Z" }
        opis: { type: string, description: Official Polish PKD description (never translated). }

    KrsData:
      type: object
      description: Parsed current KRS extract.
      properties:
        numerKRS: { type: string, example: "0000028860" }
        nazwa: { type: string }
        formaPrawna: { type: string, example: "SPÓŁKA AKCYJNA" }
        nip: { type: string }
        regon: { type: string }
        opp: { type: boolean, description: Whether the entity is a public-benefit organisation (OPP). }
        adres: { type: string }
        kapital: { type: [string, "null"], description: Share capital with currency. }
        organ: { type: [string, "null"], description: Representing body, e.g. ZARZĄD. }
        sposobReprezentacji: { type: [string, "null"] }
        sklad:
          type: array
          items: { type: string }
          description: Roles in the representing body (individuals' names are masked in the open KRS API).
        pkdMain:
          type: array
          items: { $ref: "#/components/schemas/PkdItem" }
        pkdOther:
          type: array
          items: { $ref: "#/components/schemas/PkdItem" }
        dataRejestracji: { type: string }
        dataOstatniegoWpisu: { type: string }
        stanZDnia: { type: string }
        dataCzasOdpisu: { type: string }

    NipResult:
      type: object
      properties:
        nip: { type: string }
        source:
          type: string
          enum: [krs, bl-only]
          description: "`krs` when KRS data was merged in; `bl-only` for entities not in the KRS (e.g. sole traders)."
        bl:
          type: object
          description: White List (Biała Lista) data from the Ministry of Finance.
          allOf:
            - $ref: "#/components/schemas/VatFields"
          properties:
            nip: { type: string }
            name: { type: [string, "null"] }
            regon: { type: [string, "null"] }
            address: { type: [string, "null"] }
            krs: { type: [string, "null"] }
            registrationLegalDate: { type: [string, "null"] }
            accountNumbers:
              type: array
              items: { type: string }
              description: Bank accounts registered on the White List (26-digit NRB).
            mfRequestDateTime: { type: string }
        krs:
          oneOf:
            - $ref: "#/components/schemas/KrsData"
            - type: "null"
          description: Present only when the entity has a KRS number and the extract was retrieved.
        krsError:
          type: [string, "null"]
          description: Set to the KRS number if the entity has one but its extract could not be fetched.
        checkedAt: { type: string, format: date }

    NipsResult:
      type: object
      properties:
        total: { type: integer }
        validCount: { type: integer, description: How many inputs were valid NIPs. }
        foundCount: { type: integer, description: How many were found in the register. }
        results:
          type: array
          items:
            type: object
            allOf:
              - $ref: "#/components/schemas/VatFields"
            properties:
              nip: { type: string }
              found: { type: boolean }
              name: { type: [string, "null"] }
              regon: { type: [string, "null"] }
              address: { type: [string, "null"] }
              krs: { type: [string, "null"] }
              registrationLegalDate: { type: [string, "null"] }
              accountNumbers:
                type: array
                items: { type: string }

    RegonResult:
      type: object
      properties:
        nip: { type: string }
        source: { type: string, enum: [regon-gus] }
        regon: { type: string }
        dane:
          type: object
          description: REGON entity data from GUS BIR.
          properties:
            regon: { type: string }
            nip: { type: string }
            nazwa: { type: string }
            wojewodztwo: { type: string }
            powiat: { type: string }
            gmina: { type: string }
            miejscowosc: { type: string }
            kodPocztowy: { type: string }
            ulica: { type: string }
            nrNieruchomosci: { type: string }
            nrLokalu: { type: string }
            typ: { type: string, description: "Entity type, e.g. P (legal person), F (natural person)." }
            silosID: { type: string }
            dataZakonczenia: { type: string }
        checkedAt: { type: string, format: date }

    KrsResult:
      type: object
      properties:
        source: { type: string, enum: [krs] }
        krs: { $ref: "#/components/schemas/KrsData" }
        checkedAt: { type: string, format: date }

    ViesResult:
      type: object
      properties:
        countryCode: { type: string }
        vatNumber: { type: string }
        valid: { type: boolean, description: "Raw VIES boolean." }
        result:
          type: string
          description: "Derived, language-neutral outcome."
          enum: [valid, not_registered, invalid_format, source_unavailable]
        name: { type: [string, "null"] }
        address: { type: [string, "null"] }
        requestDate: { type: [string, "null"] }
        checkedAt: { type: string, format: date }
