ZuploZuplo
LoginSign Up
  • 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.

config/policies.json
{ "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.

  • 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.
  • meters <object> - The meters to be used by the policy against the subscription quota.
  • meterOnStatusCodes <undefined> - A list of successful status codes and ranges "200-299, 304" that should trigger a metering call. Defaults to "200-299".
  • 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.

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);

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.
  • Entitlements are validated before usage is sent.

Read more about how policies work

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