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

# Query the governance tree

`GET /owners/:ownerId/query`

Returns a paginated, sortable, filterable view of an owner's governance tree — each node with its hierarchy position, budget configuration, and current usage. Use this endpoint to power dashboards, admin UIs, and leaderboards.

<Note>
  Usage figures are served from a periodically-refreshed read model and may lag the live counter by a few minutes. This endpoint never gates — use [Check](/documentation/governance/check-and-ingest#check) for real-time gate decisions.
</Note>

## Setup

`QueryApi` is exported from `@stigg/governance-client` but is not included in the `createGovernanceClient` factory. Instantiate it separately using the same axios instance that carries your auth headers.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import axios from 'axios';
  import { QueryApi, Configuration } from '@stigg/governance-client';

  const axiosInstance = axios.create({ maxRedirects: 0 });
  axiosInstance.interceptors.request.use(config => {
    config.headers.set('Authorization',          `Bearer ${process.env.GOVERNANCE_ACCESS_TOKEN}`);
    config.headers.set('x-stigg-account-id',     process.env.STIGG_ACCOUNT_ID);
    config.headers.set('x-stigg-environment-id', process.env.STIGG_ENVIRONMENT_ID);
    return config;
  });

  const queryApi = new QueryApi(
    new Configuration({ basePath: process.env.GOVERNANCE_API_URL }),
    process.env.GOVERNANCE_API_URL,
    axiosInstance,
  );
  ```
</CodeGroup>

***

## Basic query

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data } = await queryApi.queryControllerList('cus-acme');
  // data.data: QueryResponse[]
  // data.pagination: { next: string | null, prev: string | null }
  ```

  ```bash curl theme={null}
  curl "https://<GOVERNANCE_API_URL>/owners/cus-acme/query?limit=20" \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>"
  ```
</CodeGroup>

***

## Response shape

```json theme={null}
{
  "data": [
    {
      "entityId": "team-eng",
      "parentId": "org-acme",
      "entityType": "team",
      "capabilityId": "ai-tokens",
      "scopeEntityIds": [],
      "usageLimit": 200000,
      "currentUsage": 164000,
      "utilization": 0.82,
      "cadence": "P1M",
      "usagePeriodStart": "2026-05-01T00:00:00.000Z",
      "usagePeriodEnd": "2026-06-01T00:00:00.000Z"
    },
    {
      "entityId": "team-eng",
      "parentId": "org-acme",
      "entityType": "team",
      "capabilityId": "ai-tokens",
      "scopeEntityIds": ["model-gpt4o"],
      "usageLimit": 10000,
      "currentUsage": 2600,
      "utilization": 0.26,
      "cadence": "P1M",
      "usagePeriodStart": "2026-05-01T00:00:00.000Z",
      "usagePeriodEnd": "2026-06-01T00:00:00.000Z"
    }
  ],
  "pagination": {
    "next": "eyJpZCI6InRlYW0tZW5nIn0=",
    "prev": null
  }
}
```

| Field              | Description                                                                                            |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| `entityId`         | External ID of the entity (hierarchy node).                                                            |
| `parentId`         | External ID of the parent entity; `null` for a root node. Use this to rebuild the tree client-side.    |
| `entityType`       | External ID of the entity type (e.g., `org`, `team`, `user`).                                          |
| `capabilityId`     | External ID of the capability this budget is for.                                                      |
| `scopeEntityIds`   | The cardinality scope. `[]` is the node-wide budget; a non-empty set is a dimension-scoped sub-budget. |
| `usageLimit`       | Hard usage limit per cadence period.                                                                   |
| `currentUsage`     | Usage consumed in the current cadence period (may lag by minutes).                                     |
| `utilization`      | `currentUsage / usageLimit`. `1.0` when at or over limit; `null` if no limit.                          |
| `cadence`          | ISO-8601 reset cadence (e.g., `P1M`).                                                                  |
| `usagePeriodStart` | Start of the cadence period the snapshot belongs to.                                                   |
| `usagePeriodEnd`   | When usage resets (exclusive).                                                                         |

***

## Filtering

### Filter by capability

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data } = await queryApi.queryControllerList(
    'cus-acme',
    ['ai-tokens', 'api-calls'], // capabilityIds
  );
  ```

  ```bash curl theme={null}
  curl "https://<GOVERNANCE_API_URL>/owners/cus-acme/query?capabilityIds=ai-tokens&capabilityIds=api-calls" \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>"
  ```
</CodeGroup>

### Filter by entity type

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data } = await queryApi.queryControllerList(
    'cus-acme',
    undefined,         // capabilityIds
    undefined,         // scope
    ['team', 'user'],  // entityTypeIds
  );
  ```
</CodeGroup>

### Filter by scope

| `scope` value     | Rows included                                 |
| ----------------- | --------------------------------------------- |
| `'all'` (default) | All rows                                      |
| `'nodeWide'`      | Only node-wide budgets (`scopeEntityIds: []`) |
| `'scoped'`        | Only dimension-scoped sub-budgets             |

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data } = await queryApi.queryControllerList(
    'cus-acme',
    undefined,    // capabilityIds
    'nodeWide',   // scope
  );
  ```
</CodeGroup>

### Filter by utilization

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Entities at ≥ 80% utilization
  const { data: nearLimit } = await queryApi.queryControllerList(
    'cus-acme',
    undefined, undefined, undefined, undefined,
    0.8, // minUtilization
  );

  // Entities at or over their limit
  const { data: overLimit } = await queryApi.queryControllerList(
    'cus-acme',
    undefined, undefined, undefined, undefined,
    1.0, // minUtilization
  );
  ```

  ```bash curl theme={null}
  curl "https://<GOVERNANCE_API_URL>/owners/cus-acme/query?minUtilization=0.8" \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>"
  ```
</CodeGroup>

### Search by entity ID

Case-insensitive substring match on the entity ID.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data } = await queryApi.queryControllerList(
    'cus-acme',
    undefined, undefined, undefined,
    'team', // entityIdSearch
  );
  ```
</CodeGroup>

***

## Sorting

| `sortBy` value            | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `'utilization'` (default) | `currentUsage / usageLimit` — cross-capability safe |
| `'currentUsage'`          | Raw usage count                                     |
| `'usageLimit'`            | Configured limit                                    |
| `'scopeSize'`             | Node-wide rows first, then scoped by set size       |
| `'id'`                    | Entity ID alphabetical                              |
| `'createdAt'`             | Creation timestamp                                  |

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Teams closest to their limit, descending
  const { data } = await queryApi.queryControllerList(
    'cus-acme',
    ['ai-tokens'],    // capabilityIds
    undefined,        // scope
    ['team'],         // entityTypeIds
    undefined,        // entityIdSearch
    undefined,        // minUtilization
    undefined,        // after
    20,               // limit
    'utilization',    // sortBy
    'desc',           // order
  );
  ```

  ```bash curl theme={null}
  curl "https://<GOVERNANCE_API_URL>/owners/cus-acme/query?sortBy=utilization&order=desc&entityTypeIds=team" \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>"
  ```
</CodeGroup>

***

## Pagination

The query endpoint uses forward-only cursor pagination.

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function* queryAllNodes(ownerId: string) {
    let after: string | undefined;

    do {
      const { data } = await queryApi.queryControllerList(
        ownerId,
        undefined, undefined, undefined, undefined, undefined,
        after, // after cursor
        50,    // limit
      );
      yield* data.data;
      after = data.pagination.next ?? undefined;
    } while (after);
  }
  ```

  ```bash curl theme={null}
  # First page
  curl "https://<GOVERNANCE_API_URL>/owners/cus-acme/query?limit=10" \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>"

  # Next page — pass pagination.next from the previous response
  curl "https://<GOVERNANCE_API_URL>/owners/cus-acme/query?limit=10&after=<CURSOR>" \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>"
  ```
</CodeGroup>

`pagination.next` is `null` when you have reached the last page.

***

## `queryControllerList` parameter reference

```typescript theme={null}
queryControllerList(
  ownerId:        string,                       // required
  capabilityIds?: string[],                     // filter by capability
  scope?:         'all' | 'nodeWide' | 'scoped', // default 'all'
  entityTypeIds?: string[],                     // filter by entity type
  entityIdSearch?: string,                      // substring match on entity ID
  minUtilization?: number,                      // minimum utilization ratio
  after?:         string,                       // forward cursor
  limit?:         number,                       // 1–100, default 20
  sortBy?:        'utilization' | 'currentUsage' | 'usageLimit' | 'scopeSize' | 'id' | 'createdAt',
  order?:         'asc' | 'desc',               // default 'desc'
)
```

***

## Rebuilding the tree client-side

Each row carries `parentId`, so you can reconstruct the full hierarchy from a flat response:

<CodeGroup>
  ```typescript TypeScript theme={null}
  type TreeNode = QueryResponse & { children: TreeNode[] };

  function buildTree(rows: QueryResponse[]): TreeNode[] {
    const byId = new Map<string, TreeNode>(
      rows.map(r => [r.entityId, { ...r, children: [] }])
    );
    const roots: TreeNode[] = [];

    for (const node of byId.values()) {
      if (node.parentId && byId.has(node.parentId)) {
        byId.get(node.parentId)!.children.push(node);
      } else {
        roots.push(node);
      }
    }

    return roots;
  }
  ```
</CodeGroup>
