ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop on the web 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
Development
Policies
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
    OverviewQuickstart
    Concepts
    Guides
      Stripe IntegrationDeveloper PortalMonetization PolicySubscription DataDynamic MeteringProgrammatic MonetizationSubscription LifecyclePrivate PlansTax CollectionGoing to Production
    Reference
    TroubleshootingThird-Party IntegrationsCustom Monetization
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Guides

Monetization Policy Reference

The MonetizationInboundPolicy is the gateway enforcement mechanism. It runs on every request to a protected route, authenticates the API key, checks the customer's subscription and payment status, enforces quota, meters the request, and allows or blocks access.

Working with monetization in code? See Reading Subscription Data to inspect the plan and entitlements, Dynamic Metering to set meter values at runtime, and Programmatic Monetization to gate operations by plan.

Basic configuration

Add the policy to your policies.json:

Code
{ "name": "monetization-inbound", "policyType": "monetization-inbound", "handler": { "export": "MonetizationInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "meters": { "api_requests": 1 } } } }

Then reference it in your route's inbound policy pipeline:

Code
{ "x-zuplo-route": { "policies": { "inbound": ["monetization-inbound"] } } }

The MonetizationInboundPolicy handles API key authentication internally. It reads the API key from the Authorization header, validates it, and sets request.user. You do not need a separate API key authentication policy (api-key-inbound) on monetized routes — the monetization policy replaces it.

Configuration options

OptionTypeDefaultDescription
metersobject(none)Map of meter keys to increment values
meterOnStatusCodesstring or number[]"200-299"Status code range to meter
authHeaderstring"authorization"Header to read the API key from
authSchemestring"Bearer"Expected auth scheme prefix
cacheTtlSecondsnumber60How long to cache subscription data (minimum 60s)

meters

The meters option defines which meters to increment and by how much when a request is processed. Values must be non-negative numbers.

If meters is omitted, the policy still authenticates the API key and validates the subscription's payment status, but no usage is recorded. If meters is provided, it must contain at least one entry — an empty object throws a configuration error. To track usage at runtime instead of from static config, see the Dynamic Metering guide.

Code
// Increment the api_requests meter by 1 per request { "meters": { "api_requests": 1 } } // Increment multiple meters simultaneously { "meters": { "api_requests": 1, "api_credits": 5 } } // Increment by a fixed amount per request (expensive endpoint) { "meters": { "api_credits": 10 } }

meterOnStatusCodes

Controls which responses count toward metering. By default, only successful responses (2xx) are metered.

Code
// Only meter successful responses (default) { "meterOnStatusCodes": "200-299" } // Only meter 200 OK { "meterOnStatusCodes": "200" } // Meter success and redirects { "meterOnStatusCodes": "200-399" } // Comma-separated ranges { "meterOnStatusCodes": "200, 201, 300-304" } // Array of specific status codes { "meterOnStatusCodes": [200, 201, 202] }

The wildcard "*" is not a valid value for meterOnStatusCodes and throws a configuration error. Use a specific range like "200-599" if you want to meter most responses.

This is important for fairness: if your backend returns a 500 error, you probably don't want to charge the customer for that request.

authHeader

The header to read the API key from. Defaults to "authorization".

authScheme

The expected auth scheme prefix. Defaults to "Bearer". The policy expects the header value in the format {authScheme} {apiKey}.

cacheTtlSeconds

How long to cache subscription and entitlement data, in seconds. Defaults to 60 with a minimum of 60. Increasing this value reduces calls to the gateway service but means entitlement changes take longer to propagate.

Subscription and payment validation

The policy checks payment status on every request. Access is granted when:

  • The subscription is active and not expired
  • Payment status is paid or not_required (free plans)

When payment fails, a configurable grace period (default 3 days) allows continued access while retries are attempted. After the grace period, access is blocked until payment succeeds.

The grace period resolves in this order, with each level overriding the one below it:

  1. Customer metadata — zuplo_max_payment_overdue_days on the customer
  2. Plan metadata — zuplo_max_payment_overdue_days on the plan
  3. Bucket configuration — maxPaymentOverdueDays on the bucket's monetization configuration
  4. Default — 3 days

Set the value to 0 to block requests immediately when payment is overdue.

Read the subscription's plan, entitlements, and payment status in your own code with getSubscriptionData.

Multiple policies for different routes

Different routes can have different metering configurations. Define multiple policy instances in policies.json:

Code
[ { "name": "monetization-standard", "policyType": "monetization-inbound", "handler": { "export": "MonetizationInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "meters": { "api_requests": 1 } } } }, { "name": "monetization-ai", "policyType": "monetization-inbound", "handler": { "export": "MonetizationInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "meters": { "api_requests": 1, "tokens": 1 } } } }, { "name": "monetization-premium", "policyType": "monetization-inbound", "handler": { "export": "MonetizationInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "meters": { "api_credits": 10 } } } } ]

Apply each to the appropriate routes:

Code
// Simple lookup -> 1 request meter "/api/v1/search": { "inbound": ["monetization-standard"] } // AI endpoint -> 1 request + token metering "/api/v1/analyze": { "inbound": ["monetization-ai"] } // Premium endpoint -> 10 credits "/api/v1/bulk-export": { "inbound": ["monetization-premium"] }

Dynamic metering

For variable-cost endpoints — billing by tokens returned, records processed, or any value computed at runtime — set meter values from code with setMeters, addMeters, and getMeters instead of static config. See the Dynamic Metering guide for the full API and merge rules, and Programmatic Monetization for gating operations and enforcing quotas on runtime meters.

Error responses

The policy returns 403 Forbidden for all error conditions. Responses follow the RFC 7807 Problem Details format:

Code
{ "type": "https://httpproblems.com/http-status/403", "title": "Forbidden", "status": 403, "detail": "API Key has exceeded the allowed limit for \"api_requests\" meter.", "instance": "/api/v1/resource", "trace": { "timestamp": "2026-01-15T10:00:00Z", "requestId": "req_abc123", "buildId": "build_xyz" } }

Common error details:

Conditiondetail message
No auth header"No Authorization Header"
Wrong auth scheme"Invalid Authorization Scheme"
Empty key after the auth scheme"No key present"
Cached invalid key or 401"Authorization Failed"
Invalid API key"API Key is invalid or does not have access to the API"
Expired API key"API Key has expired."
Expired subscription"API Key has an expired subscription."
Subscription has no payment"Subscription payment status is not available."
Payment not made"Payment has not been made."
Payment overdue"Payment is overdue. Please update your payment method."
Subscription has no entitlements"Subscription entitlements are not available."
Meter not in subscription"API Key does not have \"X\" meter provided by the subscription."
Quota exhausted"API Key has exceeded the allowed limit for \"X\" meter."
Meter access denied"API Key does not have access to \"X\" meter."

Pipeline ordering

The monetization policy should be the first policy in the inbound pipeline since it handles authentication:

Code
1. monetization-inbound → Authenticates, checks subscription, enforces quota, meters usage 2. rate-limiting → (Optional) Per-second/per-minute spike protection 3. caching → (Optional) Response caching 4. → Route handler → Your API logic

If you still want per-second or per-minute rate limiting on top of monthly quotas, add a standalone rate-limiting policy after the monetization policy. These serve different purposes: monetization enforces billing quotas, while rate limiting protects against traffic spikes.

Related guides

  • Reading Subscription Data — inspect the plan, entitlements, and payment status in code.
  • Dynamic Metering — set meter values at runtime with setMeters, addMeters, and getMeters.
  • Programmatic Monetization — gate operations by plan and enforce quotas on response-derived meters.
Edit this page
Last modified on June 20, 2026
Developer PortalSubscription Data
On this page
  • Basic configuration
  • Configuration options
    • meters
    • meterOnStatusCodes
    • authHeader
    • authScheme
    • cacheTtlSeconds
  • Subscription and payment validation
  • Multiple policies for different routes
  • Dynamic metering
  • Error responses
  • Pipeline ordering
  • Related guides
JSON
JSON
JSON
JSON
JSON
JSON
JSON