Guides

Dynamic Metering

Most routes meter a fixed amount per request through the policy's 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

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

TypeScriptCode
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:

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

Read the current runtime meter values at any point:

TypeScriptCode
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, 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.

Next steps

Last modified on