> ## 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.

# Check and ingest

Governance exposes two runtime operations: **Check** and **Ingest**.

* **Check** reads the current usage and returns a gate decision. It never modifies state.
* **Ingest** records a consumption event and increments the usage counter. It never gates.

The typical pattern is: check → allow or deny based on `hasAccess` → ingest (only if allowed).

<Note>
  Governance is opt-in. If an entity has no assignment for the requested capability, Check returns `hasAccess: true` with an empty `checks` array. There is no default deny.
</Note>

<Warning>
  Both Check and Ingest are **fail-closed**. If the governance cache is unavailable, they return `503 Service Unavailable` rather than silently allowing or skipping. Plan for this in your error handling — a `503` from governance should be treated the same as any upstream dependency outage.
</Warning>

If you haven't set up the client yet, see [Setting up governance](/documentation/governance/setting-up#via-the-sdk).

***

## Check

`POST /owners/:ownerId/check`

Returns whether the requested amount of a capability is permitted, along with current usage and the applicable limit. Call this **before** allowing consumption.

### By entity IDs

Identify the governed entity by passing its IDs directly.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data: report } = await governance.check.checkControllerCheck('cus-acme', {
    entityIds:       ['team-eng'],
    capabilityId:    'ai-tokens',
    requestedAmount: 1000,
  });

  if (!report.hasAccess) {
    throw new Error('Usage limit reached');
  }
  ```

  ```bash curl theme={null}
  curl -X POST https://<GOVERNANCE_API_URL>/owners/cus-acme/check \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>" \
    -H "Content-Type: application/json" \
    -d '{
      "entityIds": ["team-eng"],
      "capabilityId": "ai-tokens",
      "requestedAmount": 1000
    }'
  ```
</CodeGroup>

### By dimensions

If you don't know the entity IDs at call time, pass the event dimensions and let the governance engine resolve the entities from `attributionKeys`.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data: report } = await governance.check.checkControllerCheck('cus-acme', {
    dimensions:      { teamId: 'team-eng', orgId: 'org-acme' },
    capabilityId:    'ai-tokens',
    requestedAmount: 1000,
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://<GOVERNANCE_API_URL>/owners/cus-acme/check \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>" \
    -H "Content-Type: application/json" \
    -d '{
      "dimensions": { "teamId": "team-eng", "orgId": "org-acme" },
      "capabilityId": "ai-tokens",
      "requestedAmount": 1000
    }'
  ```
</CodeGroup>

<Note>
  Pass either `entityIds` or `dimensions`, not both.
</Note>

### Check request fields

| Field             | Type       | Required | Description                                                                                                   |
| ----------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `entityIds`       | `string[]` | one of   | Entity IDs to check. Min 1, max 100 per call.                                                                 |
| `dimensions`      | `object`   | one of   | Non-empty key-value map. The engine resolves entities by matching keys against entity type `attributionKeys`. |
| `capabilityId`    | `string`   | yes      | The capability to check. Must exist and be of type `METER` — returns `400` otherwise.                         |
| `requestedAmount` | `number`   | no       | Amount the caller intends to consume. Defaults to `1`.                                                        |

### Check response

**`200 OK`**:

```json theme={null}
{
  "hasAccess": true,
  "checks": [
    {
      "entityId": "team-eng",
      "hasAccess": true,
      "chain": [
        {
          "entityId": "team-eng",
          "scopeEntityIds": [],
          "cadence": "P1M",
          "currentUsage": 42311,
          "usageLimit": 200000,
          "hasAccess": true
        },
        {
          "entityId": "org-acme",
          "scopeEntityIds": [],
          "cadence": "P1M",
          "currentUsage": 87450,
          "usageLimit": 1000000,
          "hasAccess": true
        }
      ]
    }
  ]
}
```

| Field                    | Description                                                                    |
| ------------------------ | ------------------------------------------------------------------------------ |
| `hasAccess`              | Top-level roll-up. `true` when every budget in every chain allows the request. |
| `checks[].entityId`      | The entity this check entry corresponds to.                                    |
| `checks[].hasAccess`     | Roll-up for this entity's chain.                                               |
| `checks[].chain[]`       | Per-node budget entries from the target entity up to the root.                 |
| `chain[].entityId`       | Entity at this node in the hierarchy.                                          |
| `chain[].scopeEntityIds` | Scope that matched. `[]` is the node-wide budget.                              |
| `chain[].cadence`        | ISO-8601 duration of the reset cadence (e.g., `P1M`).                          |
| `chain[].currentUsage`   | Consumed in the active cadence period.                                         |
| `chain[].usageLimit`     | Hard limit; `null` means usage is tracked but the limit never blocks.          |
| `chain[].hasAccess`      | `currentUsage + requestedAmount <= usageLimit`.                                |

### Finding the binding constraint

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { data: report } = await governance.check.checkControllerCheck('cus-acme', {
    entityIds:       ['team-eng'],
    capabilityId:    'ai-tokens',
    requestedAmount: 500,
  });

  if (!report.hasAccess) {
    const denied = report.checks
      .flatMap(c => c.chain)
      .find(n => !n.hasAccess);

    return {
      allowed:   false,
      entityId:  denied?.entityId,
      remaining: denied ? (denied.usageLimit ?? 0) - denied.currentUsage : 0,
    };
  }
  ```
</CodeGroup>

***

## Ingest

`POST /owners/:ownerId/ingest`

Records one or more consumption events and increments the usage counter for each `(entity, capability)` pair. Returns `204 No Content` immediately — ingest is fire-and-forget and **never gates**.

### By entity IDs

<CodeGroup>
  ```typescript TypeScript theme={null}
  await governance.ingest.ingestControllerIngest('cus-acme', {
    events: [
      { entityIds: ['team-eng'], capabilityId: 'ai-tokens', amount: 1250 },
    ],
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://<GOVERNANCE_API_URL>/owners/cus-acme/ingest \
    -H "Authorization: Bearer <ACCESS_TOKEN>" \
    -H "x-stigg-account-id: <ACCOUNT_ID>" \
    -H "x-stigg-environment-id: <ENVIRONMENT_ID>" \
    -H "Content-Type: application/json" \
    -d '{
      "events": [
        { "entityIds": ["team-eng"], "capabilityId": "ai-tokens", "amount": 1250 }
      ]
    }'
  ```
</CodeGroup>

### By dimensions

<CodeGroup>
  ```typescript TypeScript theme={null}
  await governance.ingest.ingestControllerIngest('cus-acme', {
    events: [
      {
        dimensions:   { teamId: 'team-eng', orgId: 'org-acme' },
        capabilityId: 'ai-tokens',
        amount:       1250,
      },
    ],
  });
  ```
</CodeGroup>

### Batching events

Send up to 100 events in one request.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await governance.ingest.ingestControllerIngest('cus-acme', {
    events: [
      { entityIds: ['org-acme'],  capabilityId: 'api-calls', amount: 1 },
      { entityIds: ['team-eng'],  capabilityId: 'api-calls', amount: 1 },
      { entityIds: ['team-eng'],  capabilityId: 'ai-tokens', amount: 2500 },
    ],
  });
  ```
</CodeGroup>

### Ingest event fields

| Field          | Type       | Required | Description                                                               |
| -------------- | ---------- | -------- | ------------------------------------------------------------------------- |
| `entityIds`    | `string[]` | one of   | Entity IDs to increment. Min 1, max 100 per event.                        |
| `dimensions`   | `object`   | one of   | Non-empty key-value map; engine resolves entities from `attributionKeys`. |
| `capabilityId` | `string`   | yes      | The capability to increment. Must exist and be of type `METER`.           |
| `amount`       | `number`   | yes      | Non-negative integer. Amount to add. `0` is a valid no-op.                |

***

## Full check-then-ingest pattern

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function consumeTokens(
    customerId: string,
    teamId:     string,
    tokenCount: number,
  ): Promise<{ allowed: boolean; remaining?: number }> {
    // 1. Check before consuming
    const { data: report } = await governance.check.checkControllerCheck(customerId, {
      entityIds:       [teamId],
      capabilityId:    'ai-tokens',
      requestedAmount: tokenCount,
    });

    if (!report.hasAccess) {
      const node = report.checks.flatMap(c => c.chain).find(n => !n.hasAccess);
      return { allowed: false, remaining: node ? (node.usageLimit ?? 0) - node.currentUsage : 0 };
    }

    // 2. Proceed with the operation …

    // 3. Ingest after consuming
    await governance.ingest.ingestControllerIngest(customerId, {
      events: [{ entityIds: [teamId], capabilityId: 'ai-tokens', amount: tokenCount }],
    });

    return { allowed: true };
  }
  ```
</CodeGroup>
