> ## Documentation Index
> Fetch the complete documentation index at: https://docs.topograph.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Ownership Graph

> Traverse multi-level ownership structures across countries automatically

The `graph` datapoint builds a complete ownership tree by recursively fetching shareholders across multiple countries and traversal depths. Starting from a root company, Topograph follows corporate shareholder chains (resolving each company in its local registry) to produce a full graph of entities and ownership relationships.

The result includes UBO (Ultimate Beneficial Owner) calculations based on ownership thresholds.

<Note>
  `graph` is a verification-only datapoint. The traversal resolves each node against its live local register, so it cannot complete within the onboarding deadline. Requesting `graph` with `mode: "onboarding"` fails immediately with `fast_source_unavailable`, without fetching or billing anything. See [Modes](/essentials/modes).
</Note>

## Example Request

```bash theme={null}
curl --request POST \
  --url https://api.topograph.co/v2/company \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <api-key>' \
  --data '{
    "countryCode": "AT",
    "id": "36416d",
    "dataPoints": ["graph"],
    "graphMaxBudget": 500
  }'
```

## How It Works

1. **Root fetch**: Retrieves the company profile and shareholders from the root company's registry
2. **Shareholder resolution**: For each corporate shareholder, searches the relevant country's registry to find the company
3. **Recursive traversal**: Fetches shareholders for each resolved company, building the tree level by level
4. **UBO calculation**: Identifies individuals with >25% ownership through direct or indirect chains

The traversal runs breadth-first with bounded concurrency. Each level completes before the next starts.

## Traversal Limits

The graph traversal has built-in limits to control scope and cost:

| Limit                 | Default                            | Description                                                                                             |
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Budget**            | Set by caller via `graphMaxBudget` | Maximum credit cents to spend on company lookups during traversal. The primary control for graph scope. |
| **Max depth**         | 10 levels                          | Maximum depth of shareholder chain to follow                                                            |
| **Max entities**      | 500                                | Maximum total entities (companies + individuals) in the graph                                           |
| **Execution timeout** | 6 hours                            | Safety net for stuck traversals. Should never be reached in normal operation                            |

When any limit is reached, the graph returns all data collected up to that point with a `stoppedReason` indicating why traversal stopped.

<Info>
  **Need higher limits?** If your use case requires deeper traversals or more entities, [contact us](mailto:hello@topograph.co) to discuss your requirements.
</Info>

## Stopped Reasons

The `metadata.stoppedReason` field indicates how the traversal ended:

| Reason              | Meaning                                                  |
| ------------------- | -------------------------------------------------------- |
| `completed`         | All shareholder chains were fully resolved               |
| `budget_exhausted`  | The `graphMaxBudget` limit was reached                   |
| `max_depth_reached` | Maximum traversal depth was reached                      |
| `max_nodes_reached` | Maximum entity count was reached                         |
| `timeout`           | Execution timeout was reached (indicates a system issue) |

## Response Structure

The graph result is included alongside the standard company data:

```json theme={null}
{
  "request": {
    "requestId": "uuid-v4",
    "companyId": "36416d",
    "countryCode": "AT",
    "dataStatus": {
      "dataPoints": {
        "graph": { "status": "succeeded" }
      }
    }
  },
  "graph": {
    "nodes": [
      {
        "nodeId": "AT:36416d",
        "type": "company",
        "flags": {
          "isRoot": true,
          "status": "found",
          "isUbo": false
        },
        "company": {
          "legalName": "Example Handels GmbH",
          "countryCode": "AT",
          "id": "36416d"
        }
      },
      {
        "nodeId": "FR:301189718",
        "type": "company",
        "flags": {
          "isRoot": false,
          "status": "found",
          "isUbo": false,
          "isUboProxy": true
        },
        "company": {
          "legalName": "Holding Internationale",
          "countryCode": "FR",
          "id": "301189718"
        },
        "totalOwnershipPercentage": 61
      }
    ],
    "edges": [
      {
        "id": "AT:36416d->FR:301189718",
        "fromId": "AT:36416d",
        "toId": "FR:301189718",
        "percentage": 61,
        "source": "Extracted from trade register extract"
      }
    ],
    "description": "Ownership graph with 2 entities and 1 relationship.",
    "metadata": {
      "stoppedReason": "completed",
      "amountSpent": 200,
      "companiesFetched": 2,
      "companiesSkipped": 0
    }
  }
}
```

## Node Flags

| Flag         | Type    | Description                                                                                    |
| ------------ | ------- | ---------------------------------------------------------------------------------------------- |
| `isRoot`     | boolean | The company that was originally requested                                                      |
| `status`     | string  | `found`, `pending`, `not_found`, `error`, `placeholder`, `search_resolved`, `budget_truncated` |
| `isUbo`      | boolean | Individual with >25% direct or indirect ownership                                              |
| `isUboProxy` | boolean | Company through which UBO ownership flows                                                      |

A node with `status: "budget_truncated"` is a placeholder for a corporate shareholder the traversal did not fetch (because of `graphMaxBudget`, `graphInteractive`, or a depth/entity limit). It carries the `nodeId` and a cost preview, and can be passed to `graphContinueFromNodeIds` in a follow-up request to resume from there.

## Interactive mode

Set `graphInteractive: true` to fetch only the entry company and return its direct shareholders. Companies among those shareholders come back as `budget_truncated` placeholders so the caller decides which branches to expand. Each chip click is one fetch and one billing event.

```bash theme={null}
curl --request POST \
  --url https://api.topograph.co/v2/company \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <api-key>' \
  --data '{
    "countryCode": "AT",
    "id": "36416d",
    "dataPoints": ["graph"],
    "graphInteractive": true
  }'
```

`graphInteractive` and `graphMaxBudget` are mutually exclusive. When `graphInteractive` is true, the budget value is ignored.

The response carries `graph.metadata.interactive: true` so a stored result is self-describing, and `graph.metadata.stoppedReason` reads `max_depth_reached`.

## Continuing from a previous request

To extend a graph from one or more `budget_truncated` placeholders, pass their `nodeId`s in `graphContinueFromNodeIds` and link to the parent through `mainRequestId`. `countryCode` and `id` are derived from the parent automatically.

```bash theme={null}
curl --request POST \
  --url https://api.topograph.co/v2/company \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <api-key>' \
  --data '{
    "dataPoints": ["graph"],
    "mainRequestId": "<previous-request-id>",
    "graphContinueFromNodeIds": ["FR:301189718"]
  }'
```

Cost deduplication applies across the full request tree: companies already fetched in the parent (or a sibling continuation) are not re-billed.

If the parent request was made with `graphInteractive: true`, every continuation inherits the flag. The seeded node is fetched and its own direct shareholders are surfaced as a fresh layer of placeholders, ready for the next click.

## Best Practices

<AccordionGroup>
  <Accordion title="Pick the right control: budget vs. interactive">
    Use `graphMaxBudget` when you want a single response with as much of the tree as fits within a known cost ceiling. Use `graphInteractive` when you want predictable per-step cost and a deterministic UI: one fetch per request, the rest of the tree visible only as continuation handles you choose to expand.
  </Accordion>

  <Accordion title="Set an appropriate budget">
    The `graphMaxBudget` parameter is the primary way to control non-interactive traversal scope. Start with a lower budget for exploration, then increase if you need deeper coverage. Each company lookup costs the standard price for that country.
  </Accordion>

  <Accordion title="Expect variable execution times">
    Graph traversal time depends on the number of companies, the countries involved, and registry response times. Simple structures (1-2 levels, single country) complete in seconds. Complex multi-country structures with deep chains can take several minutes. Interactive requests are bounded by a single company fetch, so they return as fast as that country's registry replies.
  </Accordion>

  <Accordion title="Handle partial results">
    When a limit is reached, the graph returns everything collected so far. Check `metadata.stoppedReason` to understand why traversal stopped and whether requesting again with a higher budget, or extending via `graphContinueFromNodeIds`, would yield more data.
  </Accordion>
</AccordionGroup>
