Guides

Reading Subscription Data

When the MonetizationInboundPolicy authenticates a request, it looks up the caller's subscription — their plan, entitlements, payment status, and billing dates — and stores it on the request context. Read that data from your own code with the static MonetizationInboundPolicy.getSubscriptionData method to make decisions, personalize responses, or log which plan a request ran on.

Where you can call it

getSubscriptionData returns data that the monetization policy puts on the context, so it only returns a value after the monetization-inbound policy has run. Call it from:

  • A custom code inbound policy placed after monetization-inbound in the route's inbound pipeline.
  • The route handler, which always runs after inbound policies.

On a route without the monetization policy — or in a policy that runs before it — the method returns undefined. Always handle that case.

The monetization-inbound policy must come before any policy that reads the subscription. If your policy runs first, the subscription data isn't on the context yet. See pipeline ordering.

Basic usage

TypeScriptCode
import { MonetizationInboundPolicy, HttpProblems, ZuploContext, ZuploRequest, } from "@zuplo/runtime"; export default async function (request: ZuploRequest, context: ZuploContext) { const subscription = MonetizationInboundPolicy.getSubscriptionData(context); if (!subscription) { return HttpProblems.forbidden(request, context, { detail: "No active subscription", }); } context.log.info(`Request on plan: ${subscription.plan.key}`); return request; }

The subscription object

getSubscriptionData returns a MonetizationSubscription. The fields you reach for most are the plan and the entitlements map:

TypeScriptCode
interface MonetizationSubscription { id: string; customerId: string; name: string; status: string; currency: string; plan: { id: string; name: string; key: string; // Stable identifier — switch on this in code version: number; description?: string; }; // Keyed by meter or feature key entitlements: Record< string, { balance: number; // Remaining allowance this period hasAccess: boolean; // false when no access or quota spent overage: number; // Usage beyond the included allowance usage: number; // Consumed this period } >; paymentStatus?: { status: "paid" | "not_required" | "pending" | "failed" | "uncollectible"; isFirstPayment: boolean; lastPaymentFailedAt?: string; lastPaymentSucceededAt?: string; }; billingCadence: string; // ISO 8601 duration, e.g. "P1M" for monthly billingAnchor: string; nextBillingDate: string; activeFrom: string; activeTo?: string; maxPaymentOverdueDays: number; accessBlocked?: boolean; createdAt: string; updatedAt: string; }

Switch on plan.key rather than plan.name in your logic — the key is a stable identifier, while the name is a display label that can change.

Reading entitlements

Each entry in entitlements describes one metered feature or static feature on the subscription. The key is the meter or feature key; the value reports the caller's standing against it:

TypeScriptCode
const subscription = MonetizationInboundPolicy.getSubscriptionData(context); const apiCalls = subscription?.entitlements["api_requests"]; if (apiCalls) { context.log.info( `api_requests — used ${apiCalls.usage}, ${apiCalls.balance} remaining`, ); }
  • hasAccess is the quickest check for "can this caller use this feature" — it's false when the plan doesn't include the feature or the quota has run out.
  • balance is the remaining allowance. A balance of 0 or less means no allowance remains.
  • usage and overage report consumption this billing period.

Caveats

  • Returns undefined when the monetization policy hasn't run. Guard every call.
  • The policy caches the data. Subscription and entitlement data is cached for up to cacheTtlSeconds (60 seconds minimum), so balance, usage, and overage can lag real-time consumption by the length of the cache window. Treat them as recent, not exact.

Next steps

Last modified on