Skip to main content
syrto_get_company_structure returns the ownership and organisational structure of a company: who controls it, who owns shares in it, who runs it, and what entities sit below it. Related tools: For company profile data (sector, location, employees), use Company overview. For financial metrics, use Company analysis. For a person’s contact details, use Person contacts.

Use this tool to

  • Find out who owns or controls a company
  • See officers, management, board members, and executives
  • Explore subsidiaries and group structure
  • Trace how a beneficial owner reaches the company, through the intermediate holdings
  • Determine if a company is foreign-owned

Arguments

string[]
required
The companies to get the structure of - 1 to 20 IDs, no duplicates, always a list even for a single company. IDs come from syrto_find_company or syrto_lookup_companies_by_tax_id.Past one company the lists inside each entry are capped much lower, because they share one response - see List caps below.
string[]
Show only officers holding a role in these categories. Omit for every officer on record.Values: GOVERNING_BODIES, ADMINISTRATION_AND_EXECUTIVES, AUDITING, REPRESENTATION_AND_AUTHORITY, EXTRAORDINARY_PROCEDURES, FUNCTIONAL_ROLES, PARTICIPATION_OWNERSHIP, CHIEF_EXECUTIVE_OFFICER, OTHER.For “who runs this company”, ["GOVERNING_BODIES", "CHIEF_EXECUTIVE_OFFICER"] is the useful pair: on a large company most rows are holders of a power of attorney (EXTRAORDINARY_PROCEDURES), which answers a different question.
string
"en" for English (default) or "it" for Italian.
The role filter runs over the roles that were read, not over every role on file. On a company filing more than 50 officer roles, a role you asked for can sit in rows this call could not reach - warning then carries an “officer roles N of M” clause and the filtered list is a partial one. Past one company only the first five officer roles per company are read, so a role filter across a batch is a sample rather than an answer.

Returns

result is { "companies": [...], "not_found": [...] } - the same shape for one ID as for twenty.
object[]
One entry per company, in the order you asked. It is shorter than your list when an ID returned nothing, so an entry’s position is not its position in your request - match entries by their own company_id.
string[]
IDs no structure came back for. This is part of a successful answer, not an error.
Each entry in companies carries:
string
The ID this entry answers for.
string
Official legal name of the company.
Legal form of the entity, e.g. "S.p.A.", "S.r.l.". null if not available.
string | null
Category of the controlling entity. Common values: FAMILY, INDUSTRIAL_GROUP, FINANCIALLY_OWNED_GROUP. null if not determined.
boolean | null
Whether the company is majority foreign-owned. null if not available.
object[]
One entry per officer - a person holding two roles appears once, with both. Each entry has:
  • name - officer name (null if not available)
  • person_id - the person’s ID, or null when the officer is a company (see Person IDs)
  • roles - list of { "role_category": ..., "role": ... }, the standardised category and the specific title
  • age - age in completed years (null for company officers or if unknown)
integer | null
Every officer role on record, before role_categories is applied and before roles are grouped onto one entry per officer. Expect it to exceed the length of officers: a person holding two roles files two rows. It is not a sign anything was withheld - warning reports that separately.
object[]
Direct shareholders. Each entry has:
  • name - shareholder name (null if not available)
  • person_id - the person’s ID, or null for a corporate shareholder
  • tax_id - the tax ID of a corporate shareholder, null for a person (see Company references)
  • share_percent - ownership percentage (null if not publicly disclosed)
  • age - age in completed years (null for corporate shareholders or if unknown)
object[]
Beneficial owners (ultimate controlling persons). Each entry has:
  • name - beneficial owner name (null if not available)
  • person_id - the person’s ID, or null if the owner is not a person
  • tax_id - the tax ID of an owner that is a company, null for a person
  • share_percent - effective ownership percentage (null if not disclosed)
  • age - age in completed years (null if unknown)
  • ownership_chains - the route or routes by which this owner reaches the company (see below)
object[][]
A list of routes, each route a list of hops ordered from the hop closest to this company out to the owner. Each hop carries name, person_id (persons only), tax_id (companies only) and its share_percent in the hop before it.A chain never changes the owner’s share_percent - that figure is already the product of one route’s hops - it explains it. An owner who holds the company directly shows as one route of one hop naming themselves; that is what direct ownership looks like here, not a data problem. Routes come most-direct first, and warning says when an owner had more than were shown.
object[]
Each entry has:
  • tax_id - Italian tax identifier (omitted on the rare entry with no tax ID on the registry)
  • name - subsidiary name (null if not available)
  • share_percent - parent’s ownership stake (null if not disclosed)
object[]
Companies controlled by the same parent entity (share ≥ 51%). Each entry has:
  • tax_id - Italian tax identifier (omitted on the rare entry with no tax ID on the registry)
  • name - company name (null if not available)
object[]
Companies that share a decision-maker with this company - an officer who sits in GOVERNING_BODIES or is CHIEF_EXECUTIVE_OFFICER in both. Without that restriction the list fills with companies that merely use the same audit firm, whose staff sit on hundreds of boards. role_categories does not adjust it; that parameter narrows the officers list only. Each entry has:
  • tax_id - Italian tax identifier (omitted on the rare entry with no tax ID on the registry)
  • name - company name (null if not available)
string | null
Present when any list was capped. It names the list and its true total - for example "officer roles 50 of 63" - so a capped list is never mistaken for a complete one. On a multi-company call it reports a count of affected companies rather than naming each one. companies_with_shared_officers is the one exception: it is capped but reports no total.
Beside result, the response carries the fields every tool returns. Here display_title is the company’s legal name, or a count when several were asked for, and subtitle says how many IDs were not found.

Person IDs

person_id is a person ID the other person tools accept. Pass it straight to syrto_get_person_contacts or to the people filter section of Search companies, with no syrto_find_person lookup in between - that one is a name search, so it can land on the wrong namesake. It is null on an officer, shareholder or owner that is a company rather than a person; a company can hold an office, such as an audit firm or a sole corporate shareholder. So null means “not a person”, never “a person we could not identify”.

Company references

subsidiaries, linked_companies and companies_with_shared_officers identify companies by tax_id, not by company ID. To analyse one of them, pass the tax IDs to syrto_lookup_companies_by_tax_id (up to 20 in one call) and use the company IDs it returns - which you can then feed to filters.company_ids in Search companies to sub-filter the participations. A tax ID may come back not_found, in which case that company is identifiable by name only. A shareholder, beneficial owner or ownership-chain hop that is a company carries a tax_id too - the same identity subsidiaries carry. To tell whether an owner is a company you already hold, compare tax IDs rather than names: two companies can share a name, and a multi-company call shortens long names. tax_id is null on a person (use person_id), on an entity the registry could not classify, and on the rare company with no tax ID on the registry. An officer that is a company carries no tax_id.

List caps

Every list is capped to avoid unbounded pagination, and the caps are lower on a multi-company call because all the companies share one response. The single-company subsidiaries cap is deliberately the same as the filters.company_ids cap, so any subsidiary list shown here fits one ID filter when resolved on a single statement basis. Across a batch, the *_total fields answer the interesting question - how big is this company’s board or group - rather than the lists themselves. What a batch is for is the fields above the lists: legal_entity_type, controlling_entity_category, is_foreign_owned, and the largest few shareholders. Ask for one company alone when you need its full board. share_percent values may be null if not publicly disclosed.

Example

Get the ownership structure of Ferrari:
Get just the decision-makers of three companies:
Result:
  • Shareholders and beneficial owners with names, ownership percentages where disclosed, a person_id for each person and a tax_id for each company
  • controlling_entity_category and is_foreign_owned for ownership context
  • Officers grouped one entry per person, each with every role they hold
  • Subsidiaries, linked companies and companies sharing a decision-maker, each identified by tax_id