---
title: "Corporate group structure"
method: GET
path: "/intelligence/group/{number}"
tags: ["Intelligence"]
---

# Corporate group structure

`GET /intelligence/group/{number}`

## Request

- Base URL: `https://api.companystack.co.uk`
- URL: `https://api.companystack.co.uk/intelligence/group/{number}`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Api-Key`

A company's place in its group. `structure` (added 2026-11-15) is the full graph of who controls whom, every level, exactly as GET /company/{number}/group-structure returns it on `level: full`: use it for anything new.

The earlier fields are unchanged and remain for existing integrations: `parent_entities` (the company's corporate PSCs, one level, by name), `subsidiaries` (companies whose corporate PSC has the SAME NAME as this company: `match_method` says exact_normalised_name or trigram_similarity, so these are name matches and can be a different company of that name; `structure` links by registration number only) and `same_address` (other companies at the registered office). `structure` is null if it could not be read; the earlier fields are still returned.

Limits of one answer: 8 levels above and 5 below, 60 companies in all (24 of them above), 10 registered owners and 25 controlled companies for any one company. Where a limit is reached `truncated` is true and a `more` node says where and how many.

## Path parameters

- `number` string, required

## Response `200`

Group relationships

- object
  - `company_number` string
  - `company_name` string
  - `parent_entities` object[]
    - `name` string
    - `natures_of_control` unknown
    - `notified_on` string, nullable
  - `subsidiaries` object[]
    - `company_number` string
    - `company_name` string, nullable
    - `status` string, nullable
    - `natures_of_control` unknown
    - `match_method` string
    - `match_similarity` number
  - `subsidiary_match_method` 'exact_normalised_name' | 'trigram_similarity' | 'none'
  - `same_address` object[]
  - `structure` object, nullable
    - `title` string: "Group structure" for kind group, "Ownership chart" otherwise
    - `company_number` string, required
    - `company_name` string, required
    - `root_id` string: The node id of the company asked about
    - `level` 'full' | 'first_level', required: full: every level, to where the register stops. first_level: the registered owners and the companies controlled directly, with `locked` counting what lies beyond
    - `has_group` boolean, required: true when the company has a corporate owner or controls a company. false otherwise (unchanged in meaning). Since 2026-10-13 a company with has_group false still has a chart of its own: see has_chart, kind and layout
    - `has_chart` boolean, required: true when there is a chart to draw (layout is not null). Every company held has one (added 2026-10-13)
    - `kind` 'group' | 'owners' | 'statement', required: group: a corporate owner or a controlled company exists (has_group true). owners: the company's own persons with significant control and nothing else. statement: no person is on the register, and one `end` node says what the register says instead: the PSC statement filed, the exemption or rule for that type of company, or that nothing is held (added 2026-10-13)
    - `as_at` string, date, nullable: The date the register copy is current to (the last change applied from the Companies House PSC stream)
    - `as_shown` string
    - `source` string
    - `matching` string: The rule a link is drawn by
    - `bands_note` string
    - `completeness_note` string
    - `nodes` object[], required
      - `id` string, required: c:<company number> for a company held; r:<record> for a person or an owner that is not linked; end:, more: and locked: for the notes about where the chart stops
      - `type` 'company' | 'individual' | 'corporate' | 'legal_person' | 'protected' | 'end' | 'more' | 'locked', required: company: a company on the register copy held. individual: an individual PSC. corporate: a corporate PSC that is not linked to a company held (registered outside the UK, a UK number not held, or an entry that cannot be matched), shown as the register describes it. legal_person: a legal person that is not a company. protected: a person whose details Companies House protects. end: why the chain stops above a company. more: owners or companies not included (a limit). locked: levels the plan does not include.
      - `level` integer, required: 0 for the company asked about; -1 for its registered owners, -2 for theirs; 1 for the companies it controls, 2 for theirs
      - `root` boolean: true on the company asked about
      - `name` string, required: As the register shows it. "Name withheld" where the person named has objected
      - `company_number` string, nullable: company: its number. corporate: the UK company number the entry gives, when it gives one that is not held
      - `href` string, nullable: company: the path of its page on companystack.co.uk
      - `status` string, nullable: company: its status on the register
      - `status_label` string, nullable
      - `status_class` 'active' | 'closed' | 'insolvency' | 'other' | 'null', nullable
      - `born` object, nullable: individual: month and year of birth as the register gives them. Never the day
        - `month` integer
        - `year` integer
      - `registered` string, nullable: corporate and legal_person: the place of registration and registration number as filed
      - `detail` string, required: One short line: "01234567 · Active", "Individual · born Mar 1968", "Registered: Jersey, no. 123456"
      - `note` string: end: what the register says where no person is named, short enough for a box: the PSC statement in its own words, or the rule that applies to the company's type
      - `stop` object, nullable: Why the chain stops at this node, in plain words. null for a company whose owners are shown
        - `code` 'individual' | 'protected' | 'overseas' | 'not_held' | 'unlinked' | 'legal_person' | 'name_withheld' | 'no_registrable_person' | 'psc_not_identified' | 'statement_filed' | 'no_psc_recorded' | 'outside_psc_rules' | 'possibly_exempt_plc' | 'level_limit' | 'chart_limit'
        - `label` string
        - `text` string
      - `other_owners` integer: A company below the one asked about: its other current PSCs, which are not included
      - `count` integer: more and locked: how many are not included
    - `edges` object[], required
      - `id` string, required
      - `from` string, required: The node id of the person or company with significant control
      - `to` string, required: The node id of the company controlled
      - `kind` 'control' | 'stop' | 'more', required: control: a PSC entry on the register. stop and more: the link from a note to the company it is about
      - `natures` string[], required: Nature of control codes as filed, e.g. ownership-of-shares-75-to-100-percent
      - `natures_text` string[], required: The same in words, in the register's bands: "75% or more of shares". A band, never an exact holding
      - `label` string[]: Up to three short lines for a chart: the main nature of control and how many more were filed
      - `notified_on` string, date, nullable
      - `in_cycle` boolean: Both ends are in a circle of control (see cycles)
    - `levels` object
      - `above` integer
      - `below` integer
    - `counts` object
      - `companies_above` integer
      - `companies_below` integer
      - `individuals` integer
      - `other_owners` integer
      - `owners` integer: Current persons with significant control of the company asked about, after suppressions
      - `former_owners` integer: Its ceased PSC records, which are not included
    - `stops` object[]: Every place the chain stops above the company, with the reason
      - `node_id` string
      - `name` string
      - `code` string
      - `text` string
    - `cycles` array[]: Circles of control on the register: the company numbers in each. Each company is one node however it is reached
      - string[]
    - `notes` string[]
    - `omitted` object
      - `former_links` integer: Ceased PSC records, which are not included
      - `unconfirmed_links` integer: Entries that carry a company's number as an owner without naming a UK register: not links
    - `truncated` boolean: A limit was reached; `more` nodes say where
    - `limits` object: The limits of one answer
      - `levelsUp` integer
      - `levelsDown` integer
      - `companiesUp` integer
      - `companies` integer
      - `ownersPerCompany` integer
      - `ownersOfSubject` integer: Registered owners of the company asked about that are included
      - `subsidiariesPerCompany` integer
    - `text` object: The same structure in sentences
      - `above` object[]
        - `owner` string
        - `owner_detail` string
        - `company` string
        - `company_detail` string
        - `natures` string[]
        - `since` string, date, nullable
        - `level` integer
        - `sentence` string: "<owner> is on the register as a person with significant control of <company>: <natures as filed>."
      - `below` object[]
        - `owner` string
        - `owner_detail` string
        - `company` string
        - `company_detail` string
        - `natures` string[]
        - `since` string, date, nullable
        - `level` integer
        - `sentence` string: "<owner> is on the register as a person with significant control of <company>: <natures as filed>."
      - `stops` string[]
      - `notes` string[]
    - `layout` object, nullable: Where to draw each box and link, in pixels, computed once so a web page and a PDF draw the same chart. Owners are above the companies they control. A company's many childless subsidiaries (or individual owners) are listed in one box (kind stack); the company asked about has up to four owners side by side, and with more the rest are listed in one box headed "and N more". No text is smaller than 12 pixels. Until 2026-10-13 this was null when has_group was false; it is now given for every company held (null only when has_chart is false)
      - `width` number
      - `height` number
      - `boxes` object[]
        - `id` string
        - `kind` 'node' | 'stack'
        - `type` string
        - `x` number
        - `y` number
        - `w` number
        - `h` number
        - `root` boolean
        - `status_class` string, nullable
        - `href` string, nullable
        - `title` string
        - `lines` object[]
          - `text` string
          - `y` number
          - `size` number
          - `weight` 400 | 600
          - `tone` 'ink' | 'muted'
        - `items` object[]
          - `node_id` string
          - `href` string, nullable
          - `x` number
          - `y` number
          - `w` number
          - `h` number
          - `lines` object[]
            - `text` string
            - `y` number
            - `size` number
            - `weight` 400 | 600
            - `tone` 'ink' | 'muted'
          - `title` string
      - `links` object[]
        - `id` string
        - `from` string
        - `to` string
        - `kind` string
        - `points` array[]
          - number[]
        - `label` object, nullable
          - `x` number
          - `y` number
          - `w` number
          - `h` number
          - `size` number
          - `lines` string[]
        - `in_cycle` boolean
        - `title` string
    - `layout_narrow` object, nullable: The same chart for a narrow screen, in the shape of `layout`: two or more owners of one company (or childless subsidiaries) are listed in one box. null when `layout` already fits a phone (added 2026-10-13)
    - `former_note` string, nullable: The sentence that says the company's ceased PSC records are not in the chart, with the count. null when there are none (added 2026-10-13)
    - `share_register` object: Present only when a reconstructed share register is held for the company (GET /company/{number}/shareholders). Shareholders are a different register from the PSC register and are never mixed into this answer (added 2026-10-13)
      - `held` boolean
      - `as_at` string, date, nullable: The date of the latest list of shareholders it was rebuilt to
      - `label` string
      - `note` string
    - `locked` object: Level first_level, when the full structure has more
      - `levels_above` integer
      - `levels_below` integer
      - `companies` integer
      - `message` string

## Other responses

- `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 found
- `429`: Per-minute or per-day limit reached (`code: rate_limited`, `window: minute|day`). Wait `Retry-After` seconds; when both windows are full the day is reported. A refused request does not count against either limit. A bulk lookup (`POST /company/bulk`) counts each company number in it as one request, so one that is more than you have left is refused whole, and the body adds `requested` and `fits`.

---

[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)
