ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Getting Started
    Develop in the portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
API Management
    Overview
    API Keys
    Rate Limiting
    Caching
    GraphQL
    Monetization
    Policies
      Overview
      Authentication
      Authorization
      Security & Validation
      Metrics, Billing & Quotas
        QuotaMoesif Analytics & BillingAmberflo Metering / BillingReadme MetricsOpenMeterMonetization
      Testing
      Request Modification
      Response Modification
      Upstream Authentication
      GraphQL
      Caching
      Other
      Guides
    Handlers
AI Gateway
MCP Gateway
MCP Server
Developer Portal
Development
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsVersion Support PolicySecuritySupportTrust & ComplianceChangelog
powered by Zuplo
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.

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 September 15, 2026
OpenMeterMock API Response
On this page
  • Configuration
    • Policy Configuration
    • Policy Options
  • Using the Policy
JSON
JSON
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript