Most posts about API monetization talk about counting things. Requests per month, tokens per minute, gigabytes per day. That’s metering, and it’s how usage-based pricing works.
But plan differences aren’t always about volume. Sometimes paid plans include a capability that Free doesn’t. Sometimes batch endpoints are paid-only. Sometimes Enterprise is the only plan that can write data. None of that is a meter. It’s a yes-or-no capability that one plan gets and another doesn’t.
The teams that run into this hardest are the ones who started with a single paid
plan and quietly grew to three or four. Somewhere around plan three, route
handlers are speckled with
if (plan.name === "Pro" || plan.name === "Enterprise") checks, the pricing
page is hand-maintained alongside the auth logic, and nobody wants to touch the
thing that decides who gets what.
In Zuplo, that’s a static feature, and it lives next to your meters in the plan definition.
- You have a paid plan that includes capabilities the free plan doesn't
- You're hard-coding plan checks in route handlers or stuffing flags into consumer metadata
- You want plan capabilities defined once and reflected automatically in your developer portal
Meters Count, Features Toggle
A meter answers “how much?”. A feature answers “can they?”.
If you charge $0.001 per API call with a 10,000 call free tier, that’s a meter. If only paid plans can write data, that’s a feature. Different shapes of plan logic, and treating them both as meters (or both as auth rules) ends in tears.
Zuplo splits them deliberately. Every plan can hold any number of:
- Metered features, which link to a meter and track consumption.
- Static features, which are non-metered entitlements.
“Static” in this post is the conceptual category, which is what the docs and this post mean by the term. The dashboard exposes two shapes inside that category: Boolean (on/off) for plain yes-or-no capabilities, and Static (config value) for entitlements that carry a custom value (a number, a string) rather than a yes-or-no. Capability gating wants the Boolean shape, so that’s what we’ll use throughout.
Create a Static Feature
Zuplo has native MCP server support, and “MCP access on paid plans only” is exactly the kind of decision a static feature is built for. We’ll use that as the running example.
In your project, head to Services → Monetization Service → Features and click Add Feature. The dialog is short:
- Name:
MCP Server Access. The label shown in the developer portal. - Key: auto-derived from the name (here,
mcp_server_access). You can override it before saving if you want a shorter or differently-shaped identifier. This is what you’ll read at runtime. - Linked Meter: leave on
— No meter. Without a meter, this becomes a non-metered entitlement that any plan can switch on or off.
Save, and the feature is available for any plan to include.

Pro tip:
Whether you keep the auto-derived key or override it, choose with care:
feature keys are immutable once saved. You can archive a feature, but you
can’t rename it. Pick a key you’ll still want to read in code two years from
now, and avoid baking a plan name into it (pro_mcp ages badly the moment you
add an Enterprise tier that also gets MCP).
Add the Feature to a Plan
Open the plan in Plans, click Add feature, pick the feature you just defined, and the form will ask for two things:
- Pricing Model:
Free. The feature is part of what the plan already includes, not an upsell with its own price. - Entitlement:
Boolean (on/off). The dropdown also offersStatic (config value)for entitlements that carry a custom value, but capability gating needs only the on/off form, so that’s what we pick. Saving with this set flipshasAccesstotruefor anyone on this plan.
Save, then repeat for each plan that should include the feature. Skip it on plans that shouldn’t. That’s the whole configuration.

Plan Features Reference
Full reference for static and metered features, including the entitlement template options and the API for managing features programmatically.
Check Entitlements at Runtime
This assumes you’ve already wired up the monetization-inbound policy, which
resolves the caller’s API key into a subscription (see
Part 2 of the monetized-API series
if you haven’t). Your gating policy doesn’t replace it, it reads from the data
already populated on context.
Static features show up in the same entitlements object that meters do.
MonetizationInboundPolicy.getSubscriptionData(context) returns the active
subscription, and entitlements[<feature_key>].hasAccess tells you whether the
plan includes it.
A custom inbound policy at modules/check-mcp-access.ts looks like this:
Add it as a
custom-code-inbound
policy on your /mcp route, either through the Route Designer or by adding the
policy to config/policies.json and pointing it at the module. It runs after
monetization-inbound on the same route, so unauthenticated requests have
already been rejected upstream and subscription is the active subscriber’s
record. Free callers get a clean 403 pointing at the upgrade path, paid callers
go through, and there’s no plan-name string comparison anywhere in your code.

The end-to-end version of this lives in
Part 3 of the monetized-API series,
where the same primitive gates MCP access on a fully-built monetized API. The
piece worth dwelling on, and the reason this post exists, is the dashboard side:
a static feature is two dropdowns on a plan, and once it’s there, the policy
code is the same shape every time. Swap mcp_server_access for any other
feature key and you have your next capability gate.
Custom Code Inbound Policy
Reference for the policy type used here: where modules live, the function signature, and how options flow through to the handler.
When to Use a Static Feature
Three good signals:
- The capability is yes-or-no, not counted. “Can call this endpoint” is static. “Can call it 1,000 times” is metered.
- You want it on the pricing page. Static features show up there automatically; hidden capability flags belong in consumer metadata, not plan features.
- You’d otherwise hard-code a plan check. Anywhere you’re tempted to write
if (plan.name === "Pro")is somewhere a static feature belongs.
Metering still matters, and most APIs need both. But every time you reach for a plan check that has nothing to do with usage, you’re describing a feature, and features are a primitive Zuplo already gives you.
