# Dynamic Metering

Most routes meter a fixed amount per request through the policy's
[`meters`](./monetization-policy.md#meters) option. For variable-cost endpoints
— an AI endpoint billed by tokens returned, a search billed by records matched —
the amount isn't known until the backend responds. Set meter values from code at
runtime with the `MonetizationInboundPolicy` static methods.

## The runtime metering methods

| Method                       | What it does                                                            |
| ---------------------------- | ----------------------------------------------------------------------- |
| `setMeters(context, meters)` | Replaces the runtime meter map, overriding matching static keys         |
| `addMeters(context, meters)` | Adds to the runtime meter map, accumulating with static and prior calls |
| `getMeters(context)`         | Returns the current runtime meter map                                   |

Call them from a custom policy or handler. Because the values usually come from
the response, the most common place is a custom outbound policy.

```ts
import {
  MonetizationInboundPolicy,
  ZuploContext,
  ZuploRequest,
} from "@zuplo/runtime";

// In a custom outbound policy, set meters based on the response
export default async function (
  response: Response,
  request: ZuploRequest,
  context: ZuploContext,
) {
  if (!response.ok) {
    return response;
  }

  // Reading the body consumes it, so rebuild the response afterward
  const body = (await response.json()) as {
    usage?: { total_tokens?: number };
  };
  const tokens = body.usage?.total_tokens ?? 0;

  MonetizationInboundPolicy.setMeters(context, { tokens_used: tokens });

  return new Response(JSON.stringify(body), {
    status: response.status,
    headers: response.headers,
  });
}
```

Use `addMeters` to add to existing meter values rather than replacing them:

```ts
MonetizationInboundPolicy.addMeters(context, {
  api_credits: creditsConsumed,
});
```

Read the current runtime meter values at any point:

```ts
const meters = MonetizationInboundPolicy.getMeters(context);
// { tokens_used: 150 }
```

## How meter values are merged

The final metering hook combines static and runtime values before sending usage:

- `options.meters` provides the static base values.
- `setMeters` replaces the runtime meter map, overriding matching static keys.
- `addMeters` accumulates into the runtime meter map, then combines additively
  with static values.
- When both the static and runtime maps are empty, the policy skips metering.

For a meter key like `api` with `options.meters.api = 1`:

- `setMeters(context, { api: 50 })` sends `api: 50` (replaces the static value).
- `addMeters(context, { api: 50 })` sends `api: 51` (adds to the static value).

The policy reports usage only for the status codes set by
[`meterOnStatusCodes`](./monetization-policy.md#meteronstatuscodes), so a failed
backend response costs the caller nothing.

## Enforcing quotas on runtime meters

Runtime meters set from an outbound policy run _after_ the response, so they
can't block the current request on their own. To enforce a quota on a value you
meter at runtime, declare the meter statically with a value of `0` — the policy
checks the entitlement up front without double-counting. See
[Block on a response-derived meter](./programmatic-monetization.md#block-on-a-response-derived-meter).

## Next steps

- [Programmatic Monetization](./programmatic-monetization.md) — gate operations
  by plan and enforce quotas on runtime meters.
- [Reading Subscription Data](./subscription-data.md) — inspect the plan and
  entitlements in code.
- [Monetization Policy Reference](./monetization-policy.md) — every policy
  configuration option.
- [Meters](./meters.mdx) — defining the meters you increment.
