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 for real-time gate decisions.
X-API-KEY header.
Basic query
const res = await fetch('https://api.stigg.io/api/v1/governance/owners/cus-acme/query?limit=20', {
headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! },
});
const { data, pagination } = await res.json();
// data: QueryResponse[]
// pagination: { next: string | null, prev: string | null }
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?limit=20" \
-H "X-API-KEY: <SERVER_API_KEY>"
Response shape
{
"data": [
{
"entityId": "team-eng",
"parentId": "org-acme",
"entityTypeId": "team",
"featureId": "feature_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",
"entityTypeId": "team",
"featureId": "feature_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. |
entityTypeId | External ID of the entity type (e.g., org, team, user). |
featureId | The Stigg feature or credit ID this budget governs. |
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 feature/credit
const res = await fetch(
'https://api.stigg.io/api/v1/governance/owners/cus-acme/query?featureIds=feature_ai_tokens&featureIds=feature_api_calls',
{ headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! } },
);
const { data } = await res.json();
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?featureIds=feature_ai_tokens&featureIds=feature_api_calls" \
-H "X-API-KEY: <SERVER_API_KEY>"
Filter by entity type
const res = await fetch(
'https://api.stigg.io/api/v1/governance/owners/cus-acme/query?entityTypeIds=team&entityTypeIds=user',
{ headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! } },
);
const { data } = await res.json();
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?entityTypeIds=team&entityTypeIds=user" \
-H "X-API-KEY: <SERVER_API_KEY>"
Filter by scope
scope value | Rows included |
|---|---|
'all' (default) | All rows |
'nodeWide' | Only node-wide budgets (scopeEntityIds: []) |
'scoped' | Only dimension-scoped sub-budgets |
const res = await fetch(
'https://api.stigg.io/api/v1/governance/owners/cus-acme/query?scope=nodeWide',
{ headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! } },
);
const { data } = await res.json();
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?scope=nodeWide" \
-H "X-API-KEY: <SERVER_API_KEY>"
Filter by utilization
// Entities at ≥ 80% utilization
const nearLimitRes = await fetch(
'https://api.stigg.io/api/v1/governance/owners/cus-acme/query?minUtilization=0.8',
{ headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! } },
);
const { data: nearLimit } = await nearLimitRes.json();
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?minUtilization=0.8" \
-H "X-API-KEY: <SERVER_API_KEY>"
Search by entity ID
Case-insensitive substring match on the entity ID.const res = await fetch(
'https://api.stigg.io/api/v1/governance/owners/cus-acme/query?entityIdSearch=team',
{ headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! } },
);
const { data } = await res.json();
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?entityIdSearch=team" \
-H "X-API-KEY: <SERVER_API_KEY>"
Sorting
sortBy value | Description |
|---|---|
'utilization' (default) | currentUsage / usageLimit |
'currentUsage' | Raw usage count |
'usageLimit' | Configured limit |
'scopeSize' | Node-wide rows first, then scoped by set size |
'id' | Entity ID alphabetical |
'createdAt' | Creation timestamp |
// Teams closest to their limit, descending
const res = await fetch(
'https://api.stigg.io/api/v1/governance/owners/cus-acme/query?featureIds=feature_ai_tokens&entityTypeIds=team&sortBy=utilization&order=desc&limit=20',
{ headers: { 'X-API-KEY': process.env.STIGG_SERVER_API_KEY! } },
);
const { data } = await res.json();
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?featureIds=feature_ai_tokens&entityTypeIds=team&sortBy=utilization&order=desc" \
-H "X-API-KEY: <SERVER_API_KEY>"
Pagination
The query endpoint uses forward-only cursor pagination.async function* queryAllNodes(customerId: string) {
let after: string | undefined;
const apiKey = process.env.STIGG_SERVER_API_KEY!;
do {
const url = new URL(`https://api.stigg.io/api/v1/governance/owners/${customerId}/query`);
url.searchParams.set('limit', '50');
if (after) url.searchParams.set('after', after);
const res = await fetch(url.toString(), { headers: { 'X-API-KEY': apiKey } });
const page = await res.json();
yield* page.data;
after = page.pagination.next ?? undefined;
} while (after);
}
# First page
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?limit=10" \
-H "X-API-KEY: <SERVER_API_KEY>"
# Next page — pass pagination.next from the previous response
curl "https://api.stigg.io/api/v1/governance/owners/cus-acme/query?limit=10&after=<CURSOR>" \
-H "X-API-KEY: <SERVER_API_KEY>"
pagination.next is null when you have reached the last page.
Rebuilding the tree client-side
Each row carriesparentId, so you can reconstruct the full hierarchy from a flat response:
type QueryRow = { entityId: string; parentId: string | null; [key: string]: unknown };
type TreeNode = QueryRow & { children: TreeNode[] };
function buildTree(rows: QueryRow[]): 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;
}
