# NIP, REGON, KRS, German VAT & IBAN Validator (`danielmichalek/my-actor`) Actor

Clean supplier and CRM data with bulk NIP, REGON, German VAT and IBAN format/checksum checks; KRS format only. Get normalized identifiers and clear failure reasons. No registry, VIES or bank lookups: valid does not mean active. PESEL is rejected and redacted.

- **URL**: https://apify.com/danielmichalek/my-actor.md
- **Developed by:** [Daniel Michałek](https://apify.com/danielmichalek) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 record validateds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## EU Company ID Validator (NIP, REGON, KRS, DE VAT, IBAN)

Validate Polish and German company identifiers and IBANs in bulk - fully offline, using the
official checksum algorithms. Clean up CRM exports, invoice data or supplier master data before
it reaches your ERP or payment run.

### What it does

For every input record the Actor normalizes the value (strips spaces, dashes, dots, slashes and
known prefixes), checks its format and check digit(s), and returns a structured verdict.

| Type | Identifier | What is checked |
| --- | --- | --- |
| `NIP` | Polish tax ID | 10 digits, mod-11 checksum; optional `PL` prefix. Returns formatted `123-456-32-18` and EU VAT form `PL1234563218`. |
| `REGON` | Polish business register ID | 9-digit (entity) or 14-digit (local unit) number, mod-11 checksums; for 14 digits the embedded 9-digit REGON is checked too. |
| `KRS` | Polish National Court Register number | 10 digits (leading zeros included). KRS has no check digit, so only the format is verified; short numbers get a zero-padded suggestion. |
| `DE_VAT` | German USt-IdNr | `DE` + 9 digits, ISO 7064 MOD 11,10 checksum. A bare 9-digit number is accepted and prefixed. |
| `IBAN` | International Bank Account Number | Country code, country-specific length (ISO 13616 registry) and mod-97 checksum. Returns a grouped, printable form. |

Type aliases such as `PL_NIP`, `PL_REGON`, `PL_KRS`, `USTIDNR` and `UST_IDNR` are accepted
(case-insensitive).

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `records` | array | yes | Items are either `{ "type": "NIP", "value": "123-456-32-18", "id": "row-1" }` or a bare string/number. `id` is optional and echoed back. At most 100,000 records per run. |
| `defaultType` | string | no | One of `NIP`, `REGON`, `KRS`, `DE_VAT`, `IBAN`. Used for records without their own `type`. |

Example:

```json
{
  "defaultType": "IBAN",
  "records": [
    { "id": "row-1", "type": "NIP", "value": "123-456-32-18" },
    { "id": "row-2", "type": "REGON", "value": "123456785" },
    { "id": "row-3", "type": "KRS", "value": "0000123456" },
    { "id": "row-4", "type": "DE_VAT", "value": "DE136695976" },
    "PL61 1090 1014 0000 0712 1981 2874"
  ]
}
```

### Output

One dataset item per input record, in input order:

```json
{
  "id": "row-1",
  "input": "123-456-32-18",
  "type": "NIP",
  "normalized": "1234563218",
  "valid": true,
  "reason": null,
  "message": "OK",
  "details": { "formatted": "123-456-32-18", "euVatNumber": "PL1234563218" },
  "status": "valid",
  "chargeable": true
}
```

- `status` is `valid`, `invalid` (well-formed request, but the identifier fails a check) or
  `rejected` (the request itself cannot be processed).
- `reason` explains failures: `FORMAT`, `CHECKSUM`, `BASE_CHECKSUM`, `LENGTH`, `COUNTRY`, or for
  rejected records `UNSUPPORTED_TYPE`, `EMPTY_VALUE`, `VALUE_TOO_LONG`,
  `PERSONAL_DATA_NOT_SUPPORTED`.
- A run summary (`total`, `valid`, `invalid`, `rejected`, `chargeableEvents`, `chargedEvents`, `requested`,
  `stoppedByChargeLimit`) is stored in the default key-value store under `OUTPUT`.
  `chargeableEvents` counts eligible delivered records; `chargedEvents.record-validated` counts confirmed SDK charges and is zero for an unmonetized run. Neither count proves creator revenue.

Invalid record example:

```json
{
  "id": null,
  "input": "GB82WEST12345698765431",
  "type": "IBAN",
  "normalized": "GB82WEST12345698765431",
  "valid": false,
  "reason": "CHECKSUM",
  "message": "IBAN mod-97 checksum failed",
  "details": { "country": "GB" },
  "status": "invalid",
  "chargeable": true
}
```

### Pricing (pay per event)

| Event | Charged when |
| --- | --- |
| `record-validated` | once per record with status `valid` or `invalid` |

Rejected records (unsupported type, empty or oversized value, personal identifiers) are returned
but never charged. The price per event is shown on the Actor's **Pricing** tab in Apify Store.
If your maximum charge per run is reached, the Actor stops after the last paid record and sets
`stoppedByChargeLimit: true` in `OUTPUT`.
Paid output is published only after a confirmed charge. An explicit zero budget or insufficient remaining event capacity prevents a new charge and paid result. Unmonetized development runs retain results without inventing paid events.

The complete redacted result is saved before a charge. A completed restart returns its saved summary without duplicating records or charges. If a charge or dataset acknowledgement is lost, the run stops and requires reconciliation; it does not automatically retry the effect.

### Release packaging

Release packaging: the deployment Dockerfile installs locked dependencies with TLS verification enabled and prints file SHA256 values to confirm the native source upload. The native cloud build is pending; the earlier local image passed its smoke before this packaging-only adjustment. `examples/INPUT.json`, `examples/OUTPUT.json`, and `examples/DATASET.json` preserve that local smoke's three public institutional VAT identifiers and actual results; its output reports zero charged events. Application code is unchanged.

### Limitations

- **No scraping and no network calls.** Validation is purely mathematical. The Actor does not
  query VIES, KRS, CEIDG, GUS/REGON, BZSt or any bank, so it cannot tell whether a correctly
  formed identifier is actually registered, active or VAT-registered.
- **Personal identifier types rejected.** Values explicitly labeled `PESEL`, German
  `Steuer-ID`/`IdNr`, or another supported personal identifier type are rejected and replaced
  by `[REDACTED]` in results. Company identifiers, IBANs, and user-supplied row IDs can still
  contain personal data. Do not put personal information in row IDs or other metadata.
  Original input and stored run data are retained according to the Actor's Apify storage
  settings; result redaction does not remove the original supplied input.
- **KRS** numbers carry no check digit - only the 10-digit format is verified.
- **DE_VAT** covers German numbers only; other EU VAT formats are reported as `FORMAT` errors.
- **IBAN** lengths follow the ISO 13616 registry table built into the Actor; national BBAN
  internal check digits (e.g. bank sort code checks) are not verified.
- Values longer than 64 characters are rejected.
- Serialized input is limited to 1 MB, including unknown metadata. Row IDs are limited to 128 characters; unsupported type labels and echoed rejected values are limited to 64 characters. These output fields are truncated at their limits.
- The 100,000-record ceiling is an input limit, not a guarantee that a batch fits the hosted timeout. Each delivered record needs sequential provider storage calls. Start with a small batch and check the configured budget and runtime limits.

### Worked example: catch a one-digit mistake

`123-456-32-18` passes the NIP checksum. Change the last digit to `9` and the validator returns `CHECKSUM`: `Check digit mismatch (expected 8, got 9)`.

Try this mixed list in the Actor input:

```json
{
  "records": [
    { "id": "demo-1", "type": "NIP", "value": "123-456-32-18" },
    { "id": "demo-2", "type": "NIP", "value": "123-456-32-19" },
    { "id": "demo-3", "type": "REGON", "value": "123456785" },
    { "id": "demo-4", "type": "KRS", "value": "0000123456" },
    { "id": "demo-5", "type": "DE_VAT", "value": "DE123456788" },
    { "id": "demo-6", "type": "IBAN", "value": "GB82 WEST 1234 5698 7654 32" }
  ]
}
```

| Record | Status | What the result means |
| --- | --- | --- |
| demo-1 NIP | valid | Format and checksum passed. |
| demo-2 NIP | invalid | CHECKSUM: expected 8, received 9. |
| demo-3 REGON | valid | Nine-digit format and checksum passed. |
| demo-4 KRS | valid | Ten-digit format only; no register lookup. |
| demo-5 DE VAT | valid | Format and checksum, not VAT registration. |
| demo-6 IBAN | valid | Supported country length and mod-97, not account ownership. |

Actual local execution of the same validator core on these test fixtures returned **6 processed, 5 valid, 1 invalid, 0 rejected**. This example is not a hosted billing test and does not establish that any identifier or account is registered.

Price checked on 13 October 2026: **$0.001 per supported valid or invalid result ($1 per 1,000 results)**. Rejected inputs are not charged. No separate Actor-start or dataset-write event is configured. Always check the Pricing tab for the current price.

# Actor input Schema

## `records` (type: `array`):

List of identifiers. Each item is either an object { "type": "NIP" | "REGON" | "KRS" | "DE_VAT" | "IBAN", "value": "...", "id": "optional row id" } or a bare string/number (then "Default type" is used). Separators (spaces, dashes, dots, slashes) and country prefixes (PL, DE, KRS, IBAN) are accepted. Personal identifiers such as PESEL are rejected and redacted.

## `defaultType` (type: `string`):

Identifier type used for records that do not specify their own type (e.g. bare strings).

## Actor input object example

```json
{
  "records": [
    {
      "id": "row-1",
      "type": "NIP",
      "value": "123-456-32-18"
    },
    {
      "id": "row-2",
      "type": "REGON",
      "value": "123456785"
    },
    {
      "id": "row-3",
      "type": "KRS",
      "value": "0000123456"
    },
    {
      "id": "row-4",
      "type": "DE_VAT",
      "value": "DE136695976"
    },
    {
      "id": "row-5",
      "type": "IBAN",
      "value": "PL61 1090 1014 0000 0712 1981 2874"
    }
  ]
}
```

# Actor output Schema

## `results` (type: `string`):

Offline format and checksum results; this does not establish registration, ownership or activity.

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "records": [
        {
            "id": "row-1",
            "type": "NIP",
            "value": "123-456-32-18"
        },
        {
            "id": "row-2",
            "type": "REGON",
            "value": "123456785"
        },
        {
            "id": "row-3",
            "type": "KRS",
            "value": "0000123456"
        },
        {
            "id": "row-4",
            "type": "DE_VAT",
            "value": "DE136695976"
        },
        {
            "id": "row-5",
            "type": "IBAN",
            "value": "PL61 1090 1014 0000 0712 1981 2874"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("danielmichalek/my-actor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "records": [
        {
            "id": "row-1",
            "type": "NIP",
            "value": "123-456-32-18",
        },
        {
            "id": "row-2",
            "type": "REGON",
            "value": "123456785",
        },
        {
            "id": "row-3",
            "type": "KRS",
            "value": "0000123456",
        },
        {
            "id": "row-4",
            "type": "DE_VAT",
            "value": "DE136695976",
        },
        {
            "id": "row-5",
            "type": "IBAN",
            "value": "PL61 1090 1014 0000 0712 1981 2874",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("danielmichalek/my-actor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "records": [
    {
      "id": "row-1",
      "type": "NIP",
      "value": "123-456-32-18"
    },
    {
      "id": "row-2",
      "type": "REGON",
      "value": "123456785"
    },
    {
      "id": "row-3",
      "type": "KRS",
      "value": "0000123456"
    },
    {
      "id": "row-4",
      "type": "DE_VAT",
      "value": "DE136695976"
    },
    {
      "id": "row-5",
      "type": "IBAN",
      "value": "PL61 1090 1014 0000 0712 1981 2874"
    }
  ]
}' |
apify call danielmichalek/my-actor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danielmichalek/my-actor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xHrwDcyJBJ0t7Gvd3/builds/30KAwAu34bzvZsauR/openapi.json
