
# AI Gateway Metering (v2) Policy

Meters AI Gateway v2 usage and enforces app and ancestor usage limits.

Limits configured on parent teams or the bucket root are enforced through a server-side hierarchical check.

The authentication policy must run before this policy so the app configuration id is available for meter storage and analytics.

:::caution{title="Beta"}

This policy is in beta. You can use it today, but it may change in non-backward compatible ways before the final release.

:::

## Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

```json title="config/policies.json"
{
  "name": "my-ai-gateway-metering-v2-inbound-policy",
  "policyType": "ai-gateway-metering-v2-inbound",
  "handler": {
    "export": "AIGatewayMeteringV2InboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "limits": {
        "costs": {
          "daily": {
            "warning": {}
          },
          "monthly": {
            "warning": {}
          }
        },
        "requests": {
          "daily": {
            "warning": {}
          },
          "monthly": {
            "warning": {}
          }
        },
        "tokens": {
          "daily": {
            "warning": {}
          },
          "monthly": {
            "warning": {}
          }
        }
      },
      "throwOnFailure": false
    }
  }
}
```

### Policy Configuration

- `name` <code className="text-green-600">&lt;string&gt;</code> - The name of your policy instance. This is used as a reference in your routes.
- `policyType` <code className="text-green-600">&lt;string&gt;</code> - The identifier of the policy. This is used by the Zuplo UI. Value should be `ai-gateway-metering-v2-inbound`.
- `handler.export` <code className="text-green-600">&lt;string&gt;</code> - The name of the exported type. Value should be `AIGatewayMeteringV2InboundPolicy`.
- `handler.module` <code className="text-green-600">&lt;string&gt;</code> - The module containing the policy. Value should be `$import(@zuplo/runtime)`.
- `handler.options` <code className="text-green-600">&lt;object&gt;</code> - The options for this policy. [See Policy Options](#policy-options) below.

### Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

- `throwOnFailure` <code className="text-green-600">&lt;boolean&gt;</code> - Throw when the metering service is unavailable instead of failing open. Defaults to `false`.
- `limits` <code className="text-green-600">&lt;object&gt;</code> - Usage limits grouped by meter.
  - `costs` <code className="text-green-600">&lt;object&gt;</code> - No description available.
    - `daily` <code className="text-green-600">&lt;object&gt;</code> - No description available.
      - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
      - `limit` <code className="text-green-600">&lt;number&gt;</code> - No description available.
      - `warning` <code className="text-green-600">&lt;object&gt;</code> - No description available.
        - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
        - `threshold` <code className="text-green-600">&lt;number&gt;</code> - Percentage of the limit at which to emit a warning event.
    - `monthly` <code className="text-green-600">&lt;object&gt;</code> - No description available.
      - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
      - `limit` <code className="text-green-600">&lt;number&gt;</code> - No description available.
      - `warning` <code className="text-green-600">&lt;object&gt;</code> - No description available.
        - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
        - `threshold` <code className="text-green-600">&lt;number&gt;</code> - Percentage of the limit at which to emit a warning event.
  - `tokens` <code className="text-green-600">&lt;object&gt;</code> - No description available.
    - `daily` <code className="text-green-600">&lt;object&gt;</code> - No description available.
      - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
      - `limit` <code className="text-green-600">&lt;number&gt;</code> - No description available.
      - `warning` <code className="text-green-600">&lt;object&gt;</code> - No description available.
        - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
        - `threshold` <code className="text-green-600">&lt;number&gt;</code> - Percentage of the limit at which to emit a warning event.
    - `monthly` <code className="text-green-600">&lt;object&gt;</code> - No description available.
      - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
      - `limit` <code className="text-green-600">&lt;number&gt;</code> - No description available.
      - `warning` <code className="text-green-600">&lt;object&gt;</code> - No description available.
        - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
        - `threshold` <code className="text-green-600">&lt;number&gt;</code> - Percentage of the limit at which to emit a warning event.
  - `requests` <code className="text-green-600">&lt;object&gt;</code> - No description available.
    - `daily` <code className="text-green-600">&lt;object&gt;</code> - No description available.
      - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
      - `limit` <code className="text-green-600">&lt;number&gt;</code> - No description available.
      - `warning` <code className="text-green-600">&lt;object&gt;</code> - No description available.
        - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
        - `threshold` <code className="text-green-600">&lt;number&gt;</code> - Percentage of the limit at which to emit a warning event.
    - `monthly` <code className="text-green-600">&lt;object&gt;</code> - No description available.
      - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
      - `limit` <code className="text-green-600">&lt;number&gt;</code> - No description available.
      - `warning` <code className="text-green-600">&lt;object&gt;</code> - No description available.
        - `enabled` **(required)** <code className="text-green-600">&lt;boolean&gt;</code> - No description available.
        - `threshold` <code className="text-green-600">&lt;number&gt;</code> - Percentage of the limit at which to emit a warning event.

## Using the Policy

# AI Gateway Metering (v2)

`ai-gateway-metering-v2-inbound` records AI Gateway usage and enforces daily or
monthly limits before the provider request runs. Declare the policy in
`policies.json`, then add it to an application's `inboundPolicyChain`.

## Example

```json
{
  "name": "ai-gateway-metering-v2-inbound",
  "options": {
    "throwOnFailure": false,
    "limits": {
      "costs": {
        "monthly": {
          "enabled": true,
          "limit": 100,
          "warning": {
            "enabled": true,
            "threshold": 80
          }
        }
      },
      "requests": {
        "daily": {
          "enabled": true,
          "limit": 1000
        }
      }
    }
  }
}
```

The supported meters are `costs`, `tokens`, and `requests`. Each meter can have
`daily` and `monthly` settings. A period is enforced only when `enabled` is
`true` and `limit` is present. Usage equal to the limit is blocked.

When `throwOnFailure` is `false`, metering failures pass the request through.
Set it to `true` to fail the request instead.

## Team limits

Limits configured on a parent team or the bucket root are enforced automatically
through a server-side hierarchical check against aggregated usage. The
`options.limits` settings govern only the app running this policy; the server
walks the ancestor chain and reports the first exceeded limit.

An exceeded team or root limit follows the same configured model-fallback or 429
response path as an app limit. Ancestor enforcement cannot be disabled from the
app's policy-chain entry. When the hierarchical check is unavailable,
`throwOnFailure` controls the behavior: the default skips enforcement for that
request while continuing to meter it, and `true` fails the request.

### Known residual

Grandfathered v2 apps that already store `metadata.limits` are still evaluated
at the leaf by the server's hierarchy walk. The companion Gateway Service change
rejects introducing or modifying app metadata limits; cleaning up stale rows is
tracked separately.

## Set limits from custom code

Use a custom inbound policy to vary limits for individual requests. For example,
the following policy gives callers with a `pro` plan a higher daily request
limit:

```typescript
import {
  AIGatewayMeteringV2InboundPolicy,
  type ZuploContext,
  type ZuploRequest,
} from "@zuplo/runtime";

export default function applyPlanLimit(
  request: ZuploRequest,
  context: ZuploContext,
) {
  const dailyRequestLimit = request.user?.data.plan === "pro" ? 10_000 : 1_000;

  AIGatewayMeteringV2InboundPolicy.setLimits(context, {
    requests: {
      daily: {
        enabled: true,
        limit: dailyRequestLimit,
      },
    },
  });

  return request;
}
```

Register the module as a custom policy:

```json
{
  "name": "plan-based-ai-limits",
  "policyType": "custom-code-inbound",
  "handler": {
    "export": "default",
    "module": "$import(./modules/plan-based-ai-limits)"
  }
}
```

Place it before metering in the application's inbound policy chain:

```json
{
  "inboundPolicyChain": [
    {
      "name": "plan-based-ai-limits"
    },
    {
      "name": "ai-gateway-metering-v2-inbound"
    }
  ]
}
```

`setLimits` changes only the fields supplied for the current request. Other
settings from the metering policy remain unchanged. For example, setting only
`requests.daily.limit` preserves `requests.daily.enabled`, its warning settings,
and all monthly limits. It does not update the policy configuration or affect
later requests.

The exported `AIGatewayMeteringV2LimitOverrides` interface can be used when a
custom policy builds the override object separately. Its shape matches the
`limits` option. These values are called overrides because they temporarily
replace matching policy settings for the current request:

- Choose a meter: `costs`, `tokens`, or `requests`.
- Choose a period: `daily` or `monthly`.
- Set one or more values: `enabled`, `limit`, or `warning`.

If model filtering and fallback-model policies selected a `quotaFallback`, an
exceeded limit activates that fallback instead of returning 429. The fallback
request is still metered. Order the chain as model filtering, fallback model,
metering, then policies that may short-circuit such as semantic cache.

Read more about [how policies work](/articles/policies)
