Automated KYC Onboarding: Screen Polish Companies in 3 API Calls
Every bank, fintech, and compliance team onboarding Polish companies faces the same grind: open multiple government portals, type in a NIP or KRS number, wait for each page to load, copy-paste results into a spreadsheet, repeat. For one company it's annoying. For a hundred it's someone's entire Tuesday.
Under EU's 6th Anti-Money Laundering Directive (6AMLD) and Poland's AML Act, obligated institutions must verify beneficial ownership, screen for sanctions and insolvency, and check whether the entity is a regulated financial institution. Each check pulls from a different public registry. None of them have a public API.
Here's how to automate all three checks in a single Python pipeline using pay-per-result actors from the getregdata Apify suite -- no subscriptions, no browser automation to maintain, just structured JSON from official public registries.
The 3-check pipeline
NIP (tax ID)
|
+--[1] CRBR --> Who owns this company? (UBO check)
|
+--[2] KRZ --> Are they bankrupt or in enforcement?
|
+--[3] KNF --> Are they a regulated financial institution?
|
v
DECISION: onboard, escalate, or decline
Prerequisites
You need an Apify account with an API token. The free tier includes $5 in monthly credits, which covers roughly 600 UBO checks, 800 debtor lookups, and thousands of KNF searches.
pip install apify-client
If you're building this into an AI agent workflow, install the packaged skill instead:
git clone https://github.com/Nolpak14/getregdata.git
# The skills/regdata-kyc-aml/ directory contains the full skill for Hermes/Claude agents
Step 1: Who owns it? (CRBR)
Poland's Central Register of Beneficial Owners (CRBR) is the mandatory UBO register. Every Polish company must file their natural-person beneficial owners -- anyone with more than 25% ownership or control. Search by NIP or KRS number and get back names, citizenship, ownership percentage, and control nature.
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
def check_ubo(nip: str) -> dict:
"""Return UBO data for a Polish company by NIP."""
run = client.actor("regdata/crbr-beneficial-owners-scraper").call(
run_input={
"searchQueries": [{"nip": nip}],
}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
if not items:
return {"status": "not_found", "owners": []}
item = items[0]
return {
"status": "ok",
"company": item.get("company", {}),
"owners": item.get("beneficialOwners", []),
}
What you get: structured UBO data with full names, citizenship, ownership percentages, and the nature of control (direct shareholding, board control, etc.). The declaration status tells you whether the company has filed its CRBR declaration at all -- a red flag by itself if it hasn't.
Step 2: Are they insolvent? (KRZ)
The National Debtor Registry (KRZ) covers bankruptcy proceedings, restructuring cases, and enforcement actions for Polish entities. It's updated daily by the courts and covers nine search modes -- companies, individuals, sole traders, case signatures, proceedings, shareholders, assets, advisors.
For KYC onboarding, the company search by NIP is the fast path to flagging risk:
def check_insolvency(nip: str) -> dict:
"""Check KRZ for active insolvency/enforcement proceedings."""
run = client.actor("regdata/krz-debtor-scraper").call(
run_input={
"searchQueries": [{"nip": nip}],
"maxResults": 50,
}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
if not items:
return {"status": "clean", "proceedings": []}
item = items[0]
proceedings = item.get("proceedings", [])
return {
"status": "flagged" if proceedings else "clean",
"debtor_name": item.get("debtorName"),
"proceedings": proceedings,
}
What you get: proceeding type (bankruptcy, restructuring, enforcement), court name, filing date, status. A clean KRZ check means the company has no active insolvency or enforcement proceedings -- but it doesn't guarantee financial health. For that you'd pair it with financial statement analysis, which is a separate workflow.
Step 3: Are they a regulated institution? (KNF)
Poland's Financial Supervision Authority (KNF) maintains registries of licensed financial institutions: payment institutions, e-money issuers, credit intermediaries, lending companies, and pawnbroking operators. Over 75,000 entities across three registries.
If a company you're onboarding shows up here, it changes your risk profile: you're now dealing with a regulated financial entity, which may trigger enhanced due diligence requirements under AML rules.
def check_knf(nip: str) -> dict:
"""Check KNF registries for financial institution status."""
run = client.actor("regdata/knf-registry-scraper").call(
run_input={
"searchQueries": [{"nip": nip}],
}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
if not items:
return {"status": "not_regulated", "entries": []}
return {
"status": "regulated",
"entries": items,
}
What you get: entity type (payment institution, lending company, etc.), registry source, registration number. A company that appears here is a regulated financial entity -- your AML obligations shift accordingly.
The full pipeline
Now wire all three checks together with a decision function:
from dataclasses import dataclass
from typing import List
@dataclass
class KYCResult:
nip: str
ubo_status: str
owners: List[dict]
insolvency_status: str
insolvency_proceedings: List[dict]
knf_status: str
knf_entries: List[dict]
decision: str # "clear", "review", "decline"
flags: List[str]
def run_kyc_pipeline(nip: str) -> KYCResult:
"""Run the full 3-check KYC pipeline for a Polish NIP."""
flags = []
# Step 1: UBO
ubo = check_ubo(nip)
if ubo["status"] == "not_found":
flags.append("UBO: no CRBR record found")
# Step 2: Insolvency
insolvency = check_insolvency(nip)
if insolvency["status"] == "flagged":
flags.append(f"KRZ: {len(insolvency['proceedings'])} active proceedings")
# Step 3: KNF
knf = check_knf(nip)
if knf["status"] == "regulated":
flags.append(f"KNF: regulated entity ({knf['entries'][0].get('entityType', 'unknown')})")
# Decision logic
if insolvency["status"] == "flagged":
decision = "decline"
elif knf["status"] == "regulated" or len(flags) > 0:
decision = "review"
else:
decision = "clear"
return KYCResult(
nip=nip,
ubo_status=ubo["status"],
owners=ubo.get("owners", []),
insolvency_status=insolvency["status"],
insolvency_proceedings=insolvency.get("proceedings", []),
knf_status=knf["status"],
knf_entries=knf.get("entries", []),
decision=decision,
flags=flags,
)
# Example: batch screen your onboarding queue
nips_to_screen = ["5252002340", "5213103635", "7792308903"]
for nip in nips_to_screen:
result = run_kyc_pipeline(nip)
print(f"{nip}: {result.decision.upper()}")
if result.flags:
for flag in result.flags:
print(f" - {flag}")
print()
Typical output for a clean company:
5252002340: CLEAR
And for a company with problems:
7792308903: DECLINE
- UBO: no CRBR record found
- KRZ: 2 active proceedings
Using it as an AI agent skill
If you're running AI agents for compliance workflows, the getregdata repo has this pipeline pre-packaged as an installable skill. The regdata-kyc-aml skill wraps all three actors with proper error handling, rate limiting, and output formatting -- so an agent can run a KYC check with a single prompt:
"Run a full KYC check on NIP 5252002340 and return a pass/fail decision with supporting evidence"
The skill at github.com/Nolpak14/getregdata/skills/regdata-kyc-aml/ handles the rest.
What this pipeline gives you
- Speed: 3 API calls replaces ~15 minutes of manual portal-hopping. Batch 100 companies and you save roughly an entire workday.
- Consistency: The same checks run the same way every time. No skipping the KNF check because someone got tired.
- Audit trail: Structured JSON output from every step. Drop it into your case management system or compliance database.
- Pay-per-result: You pay only for what you query. No monthly subscription for occasional use. CRBR is $0.03/check, KRZ is $0.025/check, KNF is $0.006/check. The free tier covers roughly 600 UBO checks per month.
What it doesn't cover
This pipeline screens Polish companies only. It doesn't check sanctions lists, PEP databases, or adverse media -- those are separate data sources you'd integrate alongside these registry checks. The KRZ check tells you about active proceedings but doesn't surface historical ones that have concluded. And the UBO data is what the company declared to CRBR -- it's not independently verified.
For cross-border due diligence, the same suite has actors covering Spain (BORME corporate acts, Registro Mercantil company profiles), Austria (Ediktsdatei insolvency, WKO business directory), and France (Societe.com company data with director networks and financials).
The getregdata European Business Data Suite covers 14 actors across 4 countries. All data comes from official public registries. Pay-per-result, no subscription.
Top comments (0)