---
title: "Resolve PSC ownership chain to find ultimate beneficial owners"
method: GET
path: "/v1/company/{company_number}/psc/chain"
tags: ["PSC"]
---

# Resolve PSC ownership chain to find ultimate beneficial owners

`GET /v1/company/{company_number}/psc/chain`

## Request

- Base URL: `https://api.registrum.co.uk`
- URL: `https://api.registrum.co.uk/v1/company/{company_number}/psc/chain`
- Auth: API key in header `X-API-Key`

Resolve the PSC ownership chain for a company, traversing corporate entity PSCs recursively.

Starting from the given company, fetches its PSCs. When a PSC is a corporate entity
registered at Companies House, follows it to find *that* company's PSCs, and so on,
until reaching natural persons, foreign entities, or the depth limit.

**Terminal reasons** explain why a branch stopped:

- `natural_person` - reached an individual (ultimate beneficial owner)
- `legal_person` - reached a legal person (e.g., government body)
- `super_secure` - person's details protected by court order
- `foreign_entity` - corporate entity with no registration number filed at all
- `unverified_registry` - a registration number is filed, but it cannot be
  tied to the Companies House register (a foreign registry, or a registry
  name we do not recognise). The branch stops rather than following a number
  that may belong to an unrelated UK company. `registry_number` and
  `registry_name` on the node say what was filed.
- `depth_limit` - max traversal depth reached
- `not_found` - company not found or API error
- `cycle_detected` - circular ownership detected
- `psc_exempt` - company exempt from PSC filing (e.g., listed PLC)
- `unknown_kind` - Companies House returned a PSC kind we have not classified.
  The branch stops rather than guessing; `kind_raw` carries the raw value.

**ECCTA identity verification.** Every `individual` node in the tree - at any
depth, including the ultimate beneficial owners the chain exists to find -
carries the same four fields as `/company/{number}/psc`, with the same
meanings:

- `verification_status` - `verified` | `pending` | `overdue` | `unknown`
- `identity_verified` - `true` (verified), `false` (**overdue only**), or
  `null` for pending and unknown. A pending person has missed nothing.
- `identity_verified_on` - set only when `verification_status` is `verified`
- `verification_deadline` - that person's own Companies House deadline, set
  only when `pending` or `overdue`. Companies House's open-ended-window
  sentinel (9999-12-31) is never surfaced; such a person is `pending` with a
  `null` deadline.

Corporate-entity, legal-person, super-secure and unknown nodes omit these
fields entirely - a company has no personal identity to verify, so a status
on one would imply an obligation that does not exist.

Before 2026-08-17 the chain reported no verification data at all, so the same
person could appear verified on `/psc` and blank here (CH-Api#104). Every
person in the tree is classified against a single date fixed at the start of
the traversal.

**Credits:** Each company resolved in the chain costs 1 upstream API call.
The `chain_metadata.total_credits` field shows the total cost.

**Example:** `/v1/company/12345678/psc/chain?max_depth=3`

## Path parameters

- `company_number` string, required: The Companies House company number to start the chain from.

## Query parameters

- `max_depth` integer: Maximum depth to traverse the ownership chain (1-10). Each level costs 1 upstream API call per corporate entity.

## Response `200`

PSC chain resolved successfully

- PSCChainEnvelope: Response envelope for GET /v1/company/{company_number}/psc/chain.
  - `status` string: 'success' or 'error'
  - `data` PSCChainNode: A company in the ownership chain with its active PSCs. The root node also carries `chain_metadata`.
    - `company_number` string, required: Companies House company number of this node
    - `company_name` string, nullable: Company name; null for deeper nodes not already cached, or when the name could not be fetched
    - `pscs` PSCChainEntry[]: Active PSCs of this company (ceased PSCs are skipped), or a single marker entry when the branch stopped
      - `kind` 'individual' | 'corporate-entity' | 'legal-person' | 'super-secure' | 'unknown' | 'exempt' | 'cycle' | 'error', required: Normalised PSC kind, or a marker kind (`exempt`, `cycle`, `error`) for a branch that stopped without a PSC
      - `kind_raw` string, nullable: Companies House's verbatim PSC kind (e.g. `individual-person-with-significant-control`); absent on marker kinds
      - `name` string, nullable: PSC name; absent on super-secure and marker entries
      - `terminal` boolean, required: True if the traversal stopped at this entry; false when it was followed into `children`
      - `terminal_reason` 'natural_person' | 'foreign_entity' | 'unverified_registry' | 'legal_person' | 'super_secure' | 'psc_exempt' | 'depth_limit' | 'not_found' | 'cycle_detected' | 'unknown_kind', nullable: Why the branch stopped; null when the entry was followed into `children`
      - `note` string, nullable: Human-readable explanation on marker entries (`exempt`, `cycle`, `error`)
      - `description` string, nullable: Super-secure PSCs only: Companies House's description code
      - `natures_of_control` string[], nullable: Raw Companies House control type codes
      - `natures_of_control_decoded` string[], nullable: Human-readable control descriptions
      - `notified_on` string, nullable: Date Companies House was notified (ISO 8601)
      - `nationality` string, nullable: Individuals only: nationality
      - `country_of_residence` string, nullable: Individuals only: country of residence
      - `identity_verified` boolean, nullable: Individuals only: true if verified, false if overdue only, null if pending or unknown. Same meaning as on /psc
      - `identity_verified_on` string, nullable: Individuals only: date verification was completed (ISO 8601); set only when `verification_status` is `verified`
      - `verification_status` 'verified' | 'pending' | 'overdue' | 'unknown', nullable: Individuals only: ECCTA verification state. Same meaning as on /psc; absent on non-individual entries
      - `verification_deadline` string, nullable: Individuals only: that person's own Companies House deadline, set only when `pending` or `overdue`
      - `company_number` string, nullable: Corporate entities only: normalised Companies House number if the entity is a confirmed UK registration, else null
      - `registry_number` string, nullable: Corporate entities only: registration number as filed, at whatever registry
      - `registry_name` string, nullable: Corporate entities only: registry name as filed
      - `registry_is_companies_house` boolean, nullable: Corporate entities only: true if the registration is confirmed as Companies House, false if no registration number is filed, null if a number is filed but cannot be tied to the UK register
      - `identification` PSCIdentification: Identification details for corporate entity or legal person PSCs.
        - `country_registered` string, nullable: Country of registration
        - `legal_authority` string, nullable: Governing legal authority
        - `legal_form` string, nullable: Legal form (e.g., 'Limited Company')
        - `place_registered` string, nullable: Place of registration (e.g., 'England And Wales')
        - `registration_number` string, nullable: Registration number at the registering authority
      - `children` PSCChainNode (recursive)
    - `chain_metadata` PSCChainMetadata: Traversal statistics for a PSC chain.
      - `companies_resolved` integer, required: Number of distinct companies whose PSC register was fetched
      - `max_depth_reached` boolean, required: True if any branch stopped at `max_depth`
      - `depth_used` integer, required: Number of distinct companies visited (the traversal's size, not its tree height)
      - `unresolved_branches` integer, required: Branches that stopped on `depth_limit`, `not_found` or `cycle_detected`
      - `total_credits` integer, required: Upstream Companies House calls spent resolving the chain (one per company); a measure of chain size, not of API quota spent
  - `cached` boolean: True if this response was served from cache rather than fetched live
  - `cache_age_seconds` integer, nullable: How many seconds ago this data was fetched from Companies House (null if not cached)
  - `fetched_at` string, nullable: ISO 8601 instant (UTC) when this data was actually fetched from Companies House. Use this, not cache_age_seconds, to show a user when the data was last checked: an age is measured at the moment this response was produced, so anything that caches the response downstream will overstate freshness by however long it held it. This instant does not drift. Null when unknown - render nothing rather than a guessed date.
  - `data_source` string: 'live' - fetched from Companies House right now; 'cached' - served from local cache (data is recent, no upstream call needed); 'cached_rate_conserved' - served from cache because the CH API budget is above 70% utilisation; data may be up to 30 days old in this state
  - `credits_used` integer: Number of API credits consumed by this request
  - `credits_remaining` integer, nullable: API credits remaining this month (null in dev mode)

## Other responses

- `400`: Invalid company number format, or the company number is empty (blank path segment)
- `401`: Missing or invalid API key
- `403`: Pro or Enterprise plan required
- `404`: Company not found at Companies House
- `422`: Validation Error
- `502`: Companies House API is temporarily unavailable
- `503`: Request shed to protect shared Companies House capacity; retry after the seconds given in the Retry-After header

---

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