---
title: "Verify a company (KYB) in one call"
method: POST
path: "/kyb/check"
tags: ["KYB"]
---

# Verify a company (KYB) in one call

`POST /kyb/check`

## Request

- Base URL: `https://api.companystack.co.uk`
- URL: `https://api.companystack.co.uk/kyb/check`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Api-Key`

Runs structured checks — existence, status, name and registered postcode match, age, overdue filings, Gazette company insolvency notices, PSC register and PSC discrepancies against the beneficial owners you supply, named directors, disqualified-director screening (it reports `unable_to_check`, never `pass`, while CompanyStack's copy of the disqualified directors register is not complete and current), sanctions screening (the company, its previous names, current PSCs and officers against every sanctions list loaded, including the UK Sanctions List (which includes the UN designations the UK gives effect to), the EU list, the US lists (OFAC and Consolidated Screening List) and the Canadian, Australian, NZ and French lists; full detail in the `sanctions` field, see GET /sanctions/company/{number}), optional ECCTA identity verification, registered-address density and net assets — and returns an overall `pass`, `refer`, `fail` or `unable_to_check` with reason codes. `unable_to_check` means nothing failed but a check that matters could not run: the officers could not be read (Companies House unreachable and no copy held), so no director was checked, or a source the check needs is missing, incomplete or out of date in CompanyStack's copy (the disqualified directors register, The Gazette's insolvency notices, the sanctions lists, the PSC register, the company's PSC statements when no PSC is registered, or identity verification when you asked for it). A missing source is never read as "nothing found". It is never a pass, and outranks `refer` (whose reasons are still listed); run the check again later. Every result is stored and can be retrieved later with GET /kyb/checks/{id} as evidence of your due diligence, with a PDF certificate at GET /kyb/checks/{id}.pdf.

Allowance: each paid API plan includes KYB checks every calendar month (UTC), counted per account and shared with KYB run from lists (Starter 100, Business 1,000; see https://companystack.co.uk/pricing). `X-KYB-Monthly-Limit` and `X-KYB-Monthly-Remaining` count them down; when the month is used the call answers 429 `kyb_allowance_used` with `Retry-After` and `resets_at` (the 1st, 00:00 UTC), before any check runs and without storing anything.

Reason codes: COMPANY_NOT_FOUND, COMPANY_DISSOLVED, COMPANY_IN_INSOLVENCY, PROPOSAL_TO_STRIKE_OFF, VOLUNTARY_ARRANGEMENT, STATUS_UNUSUAL, NAME_MISMATCH, NAME_SIMILAR, NAME_MATCHES_PREVIOUS_NAME, REGISTERED_POSTCODE_MISMATCH, NEWLY_INCORPORATED, ACCOUNTS_OVERDUE, CONFIRMATION_STATEMENT_OVERDUE, INSOLVENCY_NOTICE, STRIKE_OFF_NOTICE, NO_PSC_REGISTERED (no PSC is registered and the company has no current statement saying why), PSC_STATEMENT_OPEN_QUESTION (no PSC is registered and the company's own current statement leaves open who controls it, for example a PSC not yet identified or confirmed, a notice not answered or a restrictions notice: refer, with the statement quoted and dated), PSC_DISCREPANCY, DIRECTOR_NOT_FOUND, POSSIBLE_DISQUALIFIED_PERSON, OFFICERS_UNAVAILABLE (the officers check and every check that needs the officers: result `unable_to_check`), DISQUALIFIED_REGISTER_UNAVAILABLE, GAZETTE_UNAVAILABLE, SANCTIONS_UNAVAILABLE, PSC_REGISTER_UNAVAILABLE, PSC_STATEMENTS_UNAVAILABLE (no PSC is registered and the company's PSC statements could not be read just now, so we cannot say whether it filed a statement: never read as none), IDENTITY_VERIFICATION_UNAVAILABLE (a source missing, incomplete or out of date: result `unable_to_check`), SANCTIONS_POSSIBLE_MATCH, SANCTIONS_STRONG_MATCH (a name match on a sanctions list: refer, never fail — review it against the official entry), IDENTITY_VERIFICATION_OVERDUE. Warnings: HIGH_DENSITY_REGISTERED_ADDRESS, GAZETTE_HISTORY_PARTIAL (CompanyStack's Gazette copy does not cover the whole 24-month look-back), NEGATIVE_NET_ASSETS, IDENTITY_VERIFICATION_DUE_SOON, SANCTIONS_SCREENING_INCOMPLETE (no matches, but a list was not loaded or not refreshed recently, or some names could not be screened), NO_REGISTRABLE_PERSON_STATED (no PSC is registered and the company states it has no registrable person: the company's own statement, quoted with its date, not verified by CompanyStack and not a finding that nobody owns or controls it — enquire how it is owned or controlled; never read it as a clean result), PSC_EXEMPTION_TO_CONFIRM (a public limited company with no PSC: exempt if its voting shares are admitted to trading on a UK or EEA regulated market — confirm the listing). Organisations outside the PSC rules (overseas companies, registered societies and similar) get psc_register `not_checked`. The response also carries `facts.charges`: the number of outstanding, part-satisfied and satisfied charges on the register as at a date (or that they could not be read). It is information, not a check, and never changes the result: a charge is normal for a healthy borrower.

## Request body

- object
  - `company_number` string, required
  - `expected_name` string: Name your customer gave you
  - `expected_postcode` string: Registered office postcode you expect
  - `expected_pscs` string[]: Beneficial owners your customer declared — compared with the PSC register
  - `expected_directors` string[]: People who claim to be directors
  - `include_identity_verification` boolean: Also check ECCTA identity verification deadlines (live Companies House call, cached 24h)
  - `min_age_months` integer
  - `address_density_threshold` integer
  - `reference` string: Your own reference, stored with the check

## Response `200`

Check result (also stored)

- KybCheckResult: One KYB check, as returned and as stored (routes/kyb.ts).
  - `check_id` string, required
  - `company_number` string, required
  - `company_name` string, nullable
  - `reference` string, nullable
  - `outcome` 'pass' | 'refer' | 'fail' | 'unable_to_check', required: `unable_to_check`: nothing failed, but a check that matters could not run: the officers could not be read from Companies House and no copy is held (reason code OFFICERS_UNAVAILABLE), so no director was checked, or a register the check needs, such as the disqualified directors register, is not complete or current. Never a pass: run the check again later.
  - `summary` string
  - `reason_codes` string[], required
  - `warnings` string[]
  - `checks` object[], required
    - `check` string
    - `result` 'pass' | 'refer' | 'fail' | 'warn' | 'not_checked' | 'unable_to_check': `not_checked`: does not apply, or nothing is recorded to check. `unable_to_check`: should have run but a source could not be read just now, or a register it needs is not complete or current.
    - `reason_code` string, nullable
    - `message` string
    - `evidence` object
  - `sanctions` KybSanctions: Sanctions screening as a KYB check stores it. A name match is not a finding that anyone is sanctioned; each needs review. `strong_match` is shown as "Close name match" and needs date of birth or nationality / country corroboration; no name match is not a clearance.
    - `verdict` 'no_match' | 'possible_match' | 'strong_match' | 'unable_to_check', required: `unable_to_check`: no name match among the names screened, but the officers could not be read, so no director was screened.
    - `officers_status` 'cached' | 'fetched' | 'unavailable': How the officers were read: from the officers cache, from Companies House at the time, or not at all (not screened). Absent in checks stored before 2026-10-16.
    - `summary` string, required
    - `screened_at` string, date-time, required
    - `lists` object[], required: Every list: `current` and `stale` lists were screened (stale: not refreshed for 48 hours); `not_loaded` lists were not.
      - `id` 'uk_fcdo' | 'un_sc' | 'eu_fsf' | 'us_ofac_sdn' | 'us_ofac_cons' | 'ca_sema' | 'au_dfat' | 'nz_mfat' | 'jp_mof' | 'fr_tresor' | 'us_trade_csl' | 'ch_seco'
      - `name` string
      - `status` 'current' | 'stale' | 'not_loaded'
      - `published_at` string, date, nullable: The list's own publication date
      - `checked_at` string, date-time, nullable
      - `note` string, nullable
    - `subjects` object[], required
      - `role` string
      - `name` string
      - `result` 'no_match' | 'possible_match' | 'strong_match'
    - `not_screened` object[], required
      - `role` string
      - `name` string, nullable
      - `reason` string
    - `matches` object[], required
      - `subject_role` string
      - `subject_name` string
      - `verdict` 'possible_match' | 'strong_match'
      - `score` number: For sorting only: not a probability that the subject is the listed party, and not displayed
      - `list_id` string
      - `list_name` string
      - `entry_id` string
      - `listed_name` string
      - `matched_name` string
      - `programmes` string[]
      - `listed_on` string, date, nullable
      - `url` string, nullable
      - `agreed` string[]
      - `disagreed` string[]
      - `reasons` string[]
  - `facts` KybFacts
    - `charges` KybCharges: Counts of the company's charges as at a date, from CompanyStack's copy when it is proved complete and otherwise read from Companies House (every page). When they could not be read `available` is false: that is not a statement that the company has none.
      - `available` boolean, required
      - `total` integer: Every charge on the register, whatever its status (present when available)
      - `outstanding` integer
      - `part_satisfied` integer
      - `satisfied` integer
      - `other` integer: Charges with another status; total minus the three counts above
      - `as_at` string, date-time: When the list was last known to be current
      - `source` 'stored' | 'live'
      - `reason` string: Why the charges could not be read (present when available is false): companies_house_unavailable, companies_house_not_configured, incomplete_answer or list_too_long
      - `note` string, required
  - `data_as_of` string, nullable
  - `checked_at` string, date-time, required
  - `disclaimer` string: What the check covered, naming only the sanctions lists it actually screened
  - `notice` string: Who the data is prepared for and that CompanyStack accepts no responsibility to anyone else who relies on it (Terms 15B). Keep it with the data if you show it to others (Terms 17.5).

## Other responses

- `400`: Validation error
- `401`: API key missing, malformed or revoked
- `403`: The key's plan does not include this endpoint (`code: plan_upgrade_required`, with `upgrade_url`), the key may not call it, or the key is beyond the plan's key limit after a downgrade (`code: key_limit_exceeded`: the newest keys are paused until keys are deleted or the plan upgraded)
- `404`: Company not on the register (the result, outcome fail / COMPANY_NOT_FOUND, is still returned and stored)
- `429`: This month's KYB checks are used (`code: kyb_allowance_used`), or the request quota is reached (`code: rate_limited`). Retry-After says when.

---

[API](https://skmtc.dev/companystack/apis/companystack-api.md) · [All operations](https://skmtc.dev/companystack/apis/companystack-api/llms.txt) · [OpenAPI document](https://skmtc.dev/companystack/apis/companystack-api/revisions/8f3234237d2d?raw)
