Skip to content

KYB Status Verification

Know Your Business (KYB) workflows need one question answered fast: is this counterparty currently in good standing with their national registry? This guide shows you how to answer that — for one company or thousands — and how to handle the case where the answer isn't a clean yes/no.

What FirmaDB returns

Every company record carries a normalized status plus a status_active boolean and a data_freshness block telling you how recently the value was confirmed against the source.

status status_active Meaning
active true Trading entity in good standing
dissolved false Wound up, struck off, liquidated
suspended false Trading temporarily suspended (e.g., admin penalty)
unknown false Status field not populated for this record

unknown is NOT active

If status_active is false and status is unknown, FirmaDB has the record but no usable status flag for it. Do not silently treat this as a pass — escalate to manual review using the source_url.

Always preserve these three fields end-to-end into your KYB output:

  • status and status_active — the decision itself
  • data_freshness.last_confirmed_at — when FirmaDB last reconciled with the registry
  • source_url — the original government page, for the auditor

Single-counterparty check

For one entity, hit the lookup endpoint and read the status fields.

curl "https://api.firmadb.com/v1/companies/GB/00445790" \
  -H "Authorization: Bearer $FIRMADB_API_KEY"
import httpx, os

resp = httpx.get(
    "https://api.firmadb.com/v1/companies/GB/00445790",
    headers={"Authorization": f"Bearer {os.environ['FIRMADB_API_KEY']}"},
)
company = resp.json()
if company["status_active"]:
    print(f"{company['name']}: ACTIVE (confirmed {company['data_freshness']['last_confirmed_at']})")
else:
    print(f"{company['name']}: {company['status'].upper()} — verify at {company['source_url']}")
const company = await fetch(
  "https://api.firmadb.com/v1/companies/GB/00445790",
  { headers: { Authorization: `Bearer ${process.env.FIRMADB_API_KEY}` } }
).then(r => r.json());

const verdict = company.status_active
  ? `ACTIVE (confirmed ${company.data_freshness.last_confirmed_at})`
  : `${company.status.toUpperCase()} — verify at ${company.source_url}`;
console.log(`${company.name}: ${verdict}`);

A typical response (trimmed to the KYB-relevant fields):

{
  "country": "FR",
  "registry_id": "552120222",
  "name": "SOCIETE GENERALE",
  "status": "active",
  "status_active": true,
  "dissolution_date": null,
  "source_url": "https://annuaire-entreprises.data.gouv.fr/etablissement/55212022200013",
  "data_freshness": {
    "last_confirmed_at": "2026-05-04T03:14:22Z",
    "freshness_class": "fresh"
  }
}

MCP shortcut: verify_status

If you're calling from an agent, firmadb_verify_status returns a compact projection of just the KYB-relevant fields — same data, smaller payload, easier for the model to reason about. See MCP Setup.

Batch screening

For periodic recertification of an entire vendor list, use the batch endpoint and iterate over the per-row results. The pattern is identical to CRM enrichment — only the post-processing differs.

import httpx, os, uuid

API = "https://api.firmadb.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FIRMADB_API_KEY']}"}

vendors = [
    {"country": "FR", "registry_id": "552120222"},
    {"country": "GB", "registry_id": "00445790"},
    {"country": "BE", "registry_id": "0403.227.515"},
]

resp = httpx.post(
    f"{API}/companies/lookup-batch",
    json={"references": vendors, "idempotency_key": str(uuid.uuid4())},
    headers=HEADERS, timeout=60,
).json()

review_queue = []
for row in resp["results"]:
    if row["match_status"] != "found":
        review_queue.append({"input": vendors[row["index"]], "reason": "not_found"})
        continue
    co = row["company"]
    if co["status"] == "unknown" or not co["status_active"]:
        review_queue.append({
            "name": co["name"],
            "status": co["status"],
            "verify_at": co["source_url"],
        })

print(f"{len(review_queue)} of {len(vendors)} vendors need manual review")

Data freshness

Every record carries a data_freshness block:

"data_freshness": {
  "last_confirmed_at": "2026-05-04T03:14:22Z",
  "freshness_class": "fresh",
  "source_refresh_cadence": "weekly"
}
freshness_class Age Use for KYB?
fresh Confirmed within the last cadence window Yes
stale Past one cadence window Treat as advisory; re-fetch before final decision
unknown No timestamp recorded Treat as stale

Compliance reviewers want timestamps

Surface last_confirmed_at next to every status verdict in your UI. "Active as of 4 May 2026" reads better in an audit log than a bare "Active" — and it's often the first thing a reviewer asks for.

Most national registries refresh weekly. A handful (e.g. BE) push daily diffs; some (e.g. CY, MD) refresh monthly. See Country Coverage for per-country cadence.

Dissolved entities

When a company is dissolved, FirmaDB returns the dissolution date in dissolution_date. Use it to distinguish a long-defunct shell from a recent winding-up.

{
  "name": "EXAMPLE TRADING LTD",
  "status": "dissolved",
  "status_active": false,
  "dissolution_date": "2024-09-12",
  "source_url": "https://find-and-update.company-information.service.gov.uk/company/12345678"
}

For recently-dissolved entities (within 90 days), most KYB policies require a closer look — assets may still be in transit and obligations may still be enforceable.

When you can't get a clean answer

unknown, stale, or not_found are all signals to escalate, not block-and-move-on. Your code should:

  1. Capture the input row and the FirmaDB response together (so the auditor can reconstruct what was checked).
  2. Surface source_url prominently so a human can verify against the registry directly.
  3. Keep a queue separate from your "passed" list. Don't fail-open into the active set.

What's next