ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference

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.

Code
{ "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

Monetization Metering 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.

Static meter configuration

Code
{ "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

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

Add (accumulate) request meter increments

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

Read current request meter increments

Code
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).

Code
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

Code
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

Code
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

Code
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

Code
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

Edit this page
Last modified on July 17, 2026
On this page
  • Configuration
    • Policy Configuration
    • Policy Options
  • Using the Policy
JSON
JSON
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript