Metrics, Billing & Quotas

Monetization Policy

The Monetization policy allows you to track and monetize the usage of your API resources, declaratively and programmatically.

Follow our official documentation API Monetization with Zuplo to get started.

Configuration

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

JSONCode
{ "name": "my-monetization-inbound-policy", "policyType": "monetization-inbound", "handler": { "export": "MonetizationInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "cacheTtlSeconds": 60, "meters": { "api_requests": 1 }, "requiredEntitlements": ["custom_domains"] } } }

Policy Configuration

  • name <string> - The name of your policy instance. This is used as a reference in your routes.
  • policyType <string> - The identifier of the policy. This is used by the Zuplo UI. Value should be monetization-inbound.
  • handler.export <string> - The name of the exported type. Value should be MonetizationInboundPolicy.
  • handler.module <string> - The module containing the policy. Value should be $import(@zuplo/runtime).
  • handler.options <object> - The options for this policy. See Policy Options below.

Policy Options

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

  • meters <object> - The meters to be used by the policy against the subscription quota.
  • requiredEntitlements <string[]> - A list of entitlement keys that the subscription must have access to (hasAccess=true) for the request to be allowed. If any required entitlement is missing or does not have access, the request will be rejected with a 403 Forbidden.
  • authHeader <string> - The name of the header with the key. Defaults to "Authorization".
  • authScheme <string> - The scheme used on the header. Defaults to "Bearer".
  • cacheTtlSeconds <number> - The time to cache authentication results for a particular key. Higher values will decrease latency. Cached results will be valid until the cache expires even in the event the key is deleted, etc. Defaults to 60.
  • meterOnStatusCodes <undefined> - A list of successful status codes and ranges "200-299, 304" that should trigger a metering call. Defaults to "200-299".

Using the Policy

The Monetization policy validates subscriptions and records usage. Meter usage is sent in a final response hook after status-code filtering.

Configuration

  • meters (optional): static meter increments applied on metered responses.
  • meterOnStatusCodes: status codes/ranges that trigger metering.
  • auth/cache settings: authHeader, authScheme, cacheTtlSeconds.

Error responses

  • Missing, malformed, invalid, or expired API-key credentials return 401 Unauthorized. When authScheme is set, the response includes a WWW-Authenticate challenge for that scheme. An empty authScheme is raw-header mode: the entire header value is the key, and WWW-Authenticate is omitted.
  • A valid API key whose subscription, payment status, or entitlements deny access returns 403 Forbidden.

Static meter configuration

JSONCode
{ "name": "monetization-inbound-policy", "policyType": "monetization-inbound", "options": { "meters": { "api": 1 }, "meterOnStatusCodes": "200-299" } }

Runtime meter updates

You can set or update meter increments at different points in a request lifecycle (for example in an inbound policy, handler, or outbound policy). The monetization policy reads the latest values in its final hook before sending usage.

Set (replace) request meter increments

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; MonetizationInboundPolicy.setMeters(context, { input_tokens: 1000, output_tokens: 250, });

Add (accumulate) request meter increments

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; MonetizationInboundPolicy.addMeters(context, { input_tokens: 500 }); MonetizationInboundPolicy.addMeters(context, { input_tokens: 300 });

Read current request meter increments

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; const meters = MonetizationInboundPolicy.getMeters(context);

Flush (send) runtime meters mid-request

Use flushMeters to send accumulated runtime meters immediately and reset their counts. This is useful for long-running requests where you want to report partial usage before the response is sent (for example, after each chunk of work in a streaming handler).

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; MonetizationInboundPolicy.addMeters(context, { input_tokens: 500 }); await MonetizationInboundPolicy.flushMeters(context); // Runtime meters are cleared before the backend send begins so a concurrent // final hook cannot double-count them. Meters are restored if the send fails. MonetizationInboundPolicy.addMeters(context, { input_tokens: 300 });

Static options.meters are not included in flushMeters; they are still sent in the final response hook.

Note: flushMeters performs entitlement validation and will throw if called before the monetization policy runs on the request or if you send meters that aren’t present/accessible on the subscription. Wrap in try/catch if you want to continue the request when a flush fails.

Subscription data (for customization)

The monetization policy validates the API key and attaches the subscription to the request context. You can read it later in your pipeline/handler to customize behavior (limits, feature flags, plan-based behavior, etc.).

Read subscription data

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; const subscription = MonetizationInboundPolicy.getSubscriptionData(context); if (!subscription) { // The monetization policy may not have run yet, or the request was rejected earlier. // Decide what behavior you want in this case. }

Common fields you’ll likely use

  • Identity: subscription.id, subscription.customerId, subscription.name
  • Plan: subscription.plan.key, subscription.plan.version
  • Status & dates: subscription.status, subscription.activeFrom, subscription.activeTo, subscription.nextBillingDate
  • Entitlements: subscription.entitlements[meterName] → { balance, usage, overage, hasAccess }
  • Payment (when present): subscription.paymentStatus.status, subscription.paymentStatus.isFirstPayment

Example: gate a feature by plan

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; const subscription = MonetizationInboundPolicy.getSubscriptionData(context); if (subscription?.plan.key !== "enterprise") { return new Response("This feature requires the Enterprise plan.", { status: 403, }); }

Example: check entitlement access or remaining balance

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; const subscription = MonetizationInboundPolicy.getSubscriptionData(context); const entitlement = subscription?.entitlements?.["requests"]; if (!entitlement?.hasAccess) { return new Response("Your subscription does not include this meter.", { status: 403, }); } // A simple “remaining” calculation you can use for UX or soft limits. const remaining = entitlement.balance - entitlement.usage; if (remaining <= 0) { return new Response("Quota exceeded.", { status: 429 }); }

Example: customize response headers

TypeScriptCode
import { MonetizationInboundPolicy } from "@zuplo/runtime"; const subscription = MonetizationInboundPolicy.getSubscriptionData(context); const nextResponse = new Response(response.body, response); if (subscription) { nextResponse.headers.set("x-zuplo-plan", subscription.plan.key); } return nextResponse;

Meter merge behavior

  • The final hook merges options.meters and request meter increments from setMeters / addMeters.
  • setMeters replaces the current runtime meter map and overrides matching keys from options.meters.
  • addMeters accumulates into the current runtime meter map and then merges additively with options.meters.
  • If both are empty, metering is skipped.

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

  • setMeters(context, { api: 50 }) sends api: 50.
  • addMeters(context, { api: 50 }) sends api: 51.

Prerequisites

  • monetization-inbound is enabled in your route/pipeline.
  • Meter names match entitlements on the subscription.
  • Meter quantities are finite positive numbers.

Notes

  • setMeters replaces current request meter increments.
  • addMeters accumulates values across multiple calls.
  • flushMeters sends runtime meters immediately, clears them before the backend send (to avoid duplicate sends if the final hook runs concurrently), and restores them if the send fails.
  • Entitlements are validated before usage is sent.

Read more about how policies work

Last modified on