> ## 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 `isGranted` → ingest (only if allowed).

<Note>
  Governance is opt-in. If an entity has no assignment for the requested capability, Check returns `isGranted: true` with an empty `chains` 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 initialized the client yet, see [Setting up governance](/documentation/governance/setting-up#via-the-sdk).

```typescript theme={null}
import Stigg from '@stigg/typescript';

const client = new Stigg({ apiKey: process.env.STIGG_API_KEY });
```

***

## Check

`GET /api/v1-beta/customers/:id/entitlements/check`

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

### By feature

Pass the feature ID and dimensions that identify the governed entities. The governance engine resolves the entity hierarchy from the dimensions automatically.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const response = await client.v1Beta.customers.entitlements.check('cus-acme', {
    featureId:      'feature-ai-tokens',
    requestedUsage: 1000,
    dimensions:     { teamId: 'team-eng' },
  });

  if (!response.data.isGranted) {
    throw new Error('Usage limit reached');
  }
  ```

  ```bash curl theme={null}
  curl "https://api.stigg.io/api/v1-beta/customers/cus-acme/entitlements/check?featureId=feature-ai-tokens&requestedUsage=1000&dimensions[teamId]=team-eng" \
    -H "X-API-KEY: <SERVER_API_KEY>"
  ```
</CodeGroup>

### By credit

Use `currencyId` instead of `featureId` to check against a credit balance limit.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const response = await client.v1Beta.customers.entitlements.check('cus-acme', {
    currencyId:     'currency-ai-tokens',
    requestedUsage: 1000,
    dimensions:     { teamId: 'team-eng' },
  });

  if (!response.data.isGranted) {
    throw new Error('Usage limit reached');
  }
  ```

  ```bash curl theme={null}
  curl "https://api.stigg.io/api/v1-beta/customers/cus-acme/entitlements/check?currencyId=currency-ai-tokens&requestedUsage=1000&dimensions[teamId]=team-eng" \
    -H "X-API-KEY: <SERVER_API_KEY>"
  ```
</CodeGroup>

<Note>
  Pass either `featureId` or `currencyId`, not both.
</Note>

### Check request fields

| Field            | Type     | Required | Description                                                                                                            |
| ---------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `featureId`      | `string` | one of   | The Stigg feature ID to check. Mutually exclusive with `currencyId`.                                                   |
| `currencyId`     | `string` | one of   | The Stigg credit currency ID to check. Mutually exclusive with `featureId`.                                            |
| `dimensions`     | `object` | no       | Non-empty key-value map. The engine resolves governed entities by matching keys against entity type `attributionKeys`. |
| `requestedUsage` | `number` | no       | Amount the caller intends to consume. Defaults to `1`.                                                                 |

### Check response

**`200 OK`**:

```json theme={null}
{
  "data": {
    "isGranted": true,
    "type": "FEATURE",
    "accessDeniedReason": null,
    "feature": {
      "id": "feature-ai-tokens",
      "displayName": "AI Tokens",
      "featureType": "NUMBER",
      "featureStatus": "ACTIVE"
    },
    "usageLimit": 200000,
    "hasUnlimitedUsage": false,
    "resetPeriod": "MONTH",
    "currentUsage": 42311,
    "chains": [
      [
        {
          "entityId": "team-eng",
          "scopeEntityIds": [],
          "usageLimit": 200000,
          "currentUsage": 42311,
          "isGranted": true
        },
        {
          "entityId": "org-acme",
          "scopeEntityIds": [],
          "usageLimit": 1000000,
          "currentUsage": 87450,
          "isGranted": true
        }
      ]
    ]
  }
}
```

| Field                       | Description                                                                                       |
| --------------------------- | ------------------------------------------------------------------------------------------------- |
| `isGranted`                 | Top-level gate decision. `true` when every budget in every chain allows the request.              |
| `type`                      | Entitlement type: `FEATURE` or `CREDIT`.                                                          |
| `usageLimit`                | Limit configured for this customer. `null` means usage is tracked but the limit never blocks.     |
| `currentUsage`              | Consumed in the active reset period.                                                              |
| `resetPeriod`               | The reset cadence (e.g. `MONTH`).                                                                 |
| `chains[]`                  | Per-entity budget chains for this check. Present only when governance is active for the customer. |
| `chains[][].entityId`       | Entity at this node in the hierarchy.                                                             |
| `chains[][].scopeEntityIds` | Scope that matched. `[]` is the node-wide budget.                                                 |
| `chains[][].usageLimit`     | Hard limit; `null` means usage is tracked but never blocks.                                       |
| `chains[][].currentUsage`   | Consumed in the active cadence period.                                                            |
| `chains[][].isGranted`      | `currentUsage + requestedUsage <= usageLimit`.                                                    |

### Finding the binding constraint

<CodeGroup>
  ```typescript TypeScript theme={null}
  const response = await client.v1Beta.customers.entitlements.check('cus-acme', {
    featureId:      'feature-ai-tokens',
    requestedUsage: 500,
    dimensions:     { teamId: 'team-eng' },
  });

  if (!response.data.isGranted) {
    const denied = response.data.chains
      ?.flat()
      .find((node) => !node.isGranted);

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

***

## Ingest

Records consumption and increments the usage counter. Ingest **never gates** — call Check first if you need a permit decision before consuming.

Use `reportUsage` for synchronous, real-time enforcement — credit-backed features return an updated balance in the response immediately. Use `reportEvent` for high-volume, eventually-consistent metering (≈10 s latency) where a real-time balance is not required.

### reportUsage

<CodeGroup>
  ```typescript TypeScript theme={null}
  await client.v1.usage.report({
    usages: [
      {
        customerId: 'cus-acme',
        featureId:  'feature-ai-tokens',
        value:      1250,
        dimensions: { teamId: 'team-eng' },
      },
    ],
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://api.stigg.io/api/v1/usage \
    -H "X-API-KEY: <SERVER_API_KEY>" \
    -H "Content-Type: application/json" \
    -d '{
      "usages": [
        {
          "customerId": "cus-acme",
          "featureId": "feature-ai-tokens",
          "value": 1250,
          "dimensions": { "teamId": "team-eng" }
        }
      ]
    }'
  ```
</CodeGroup>

### reportEvent

<CodeGroup>
  ```typescript TypeScript theme={null}
  await client.v1.events.report({
    events: [
      {
        customerId:     'cus-acme',
        eventName:      'ai-tokens-consumed',
        idempotencyKey: crypto.randomUUID(),
        dimensions:     { teamId: 'team-eng', tokenCount: 1250 },
      },
    ],
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://api.stigg.io/api/v1/events \
    -H "X-API-KEY: <SERVER_API_KEY>" \
    -H "Content-Type: application/json" \
    -d '{
      "events": [
        {
          "customerId": "cus-acme",
          "eventName": "ai-tokens-consumed",
          "idempotencyKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "dimensions": { "teamId": "team-eng", "tokenCount": 1250 }
        }
      ]
    }'
  ```
</CodeGroup>

### Batching usage reports

Both `reportUsage` and `reportEvent` accept up to 100 records per request.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await client.v1.usage.report({
    usages: [
      { customerId: 'cus-acme', featureId: 'feature-api-calls', value: 1,    dimensions: { teamId: 'team-eng' } },
      { customerId: 'cus-acme', featureId: 'feature-ai-tokens', value: 2500, dimensions: { teamId: 'team-eng' } },
    ],
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://api.stigg.io/api/v1/usage \
    -H "X-API-KEY: <SERVER_API_KEY>" \
    -H "Content-Type: application/json" \
    -d '{
      "usages": [
        { "customerId": "cus-acme", "featureId": "feature-api-calls", "value": 1,    "dimensions": { "teamId": "team-eng" } },
        { "customerId": "cus-acme", "featureId": "feature-ai-tokens", "value": 2500, "dimensions": { "teamId": "team-eng" } }
      ]
    }'
  ```
</CodeGroup>

### Ingest fields

| Field        | Type     | Required | Description                                                                                           |
| ------------ | -------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `customerId` | `string` | yes      | The Stigg customer ID.                                                                                |
| `featureId`  | `string` | yes      | The Stigg feature or credit ID to increment.                                                          |
| `value`      | `number` | yes      | Non-negative integer. Amount to add.                                                                  |
| `dimensions` | `object` | no       | Key-value map used for governance entity attribution. Values can be `string`, `number`, or `boolean`. |

***

## 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 response = await client.v1Beta.customers.entitlements.check(customerId, {
      featureId:      'feature-ai-tokens',
      requestedUsage: tokenCount,
      dimensions:     { teamId },
    });

    if (!response.data.isGranted) {
      const denied = response.data.chains?.flat().find((n) => !n.isGranted);
      return { allowed: false, remaining: denied ? (denied.usageLimit ?? 0) - denied.currentUsage : 0 };
    }

    // 2. Proceed with the operation …

    // 3. Ingest after consuming
    await client.v1.usage.report({
      usages: [{ customerId, featureId: 'feature-ai-tokens', value: tokenCount, dimensions: { teamId } }],
    });

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