zuplo 6.71.6 → 6.71.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -119,16 +119,14 @@ per API.
119
119
 
120
120
  Import `graphqlPlugin` and add an instance per API. The `path` is where the docs
121
121
  mount, and `schema` points at your GraphQL API — either a live endpoint URL or a
122
- path to a schema definition language (SDL) file. Define the `path` once with
123
- `createPath` (a helper exported from `zudoku`) and reference the same value from
124
- both the plugin and the navigation link, so the link can never point at a path
125
- the plugin isn't mounted at:
122
+ path to a schema definition language (SDL) file. Define the `path` once as a
123
+ const and reference the same value from both the plugin and the navigation link,
124
+ so the link can never point at a path the plugin isn't mounted at:
126
125
 
127
126
  ```tsx title="zudoku.config.tsx"
128
127
  import { graphqlPlugin } from "@zudoku/plugin-graphql";
129
- import { createPath } from "zudoku";
130
128
 
131
- const graphqlPath = createPath("/graphql");
129
+ const graphqlPath = "/graphql";
132
130
 
133
131
  const config = {
134
132
  navigation: [
@@ -36,6 +36,16 @@ certificates, you can add the following script:
36
36
  }
37
37
  ```
38
38
 
39
+ ## "unknown format ... ignored" warnings
40
+
41
+ When the development server prints warnings like
42
+ `unknown format "date-time" ignored in schema at path "…"`, your OpenAPI schema
43
+ uses a `format` keyword that the validator does not actively check. These
44
+ warnings are safe to ignore — they do not stop the server or change validation
45
+ behavior. See
46
+ [OpenAPI Format Validation Warnings](./openapi-string-format-validation-warnings.mdx)
47
+ for details.
48
+
39
49
  ## Updating the Zuplo CLI
40
50
 
41
51
  To update the CLI, run the following command in your project directory.
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: Dynamic Metering
3
+ sidebar_label: Dynamic Metering
4
+ ---
5
+
6
+ Most routes meter a fixed amount per request through the policy's
7
+ [`meters`](./monetization-policy.md#meters) option. For variable-cost endpoints
8
+ — an AI endpoint billed by tokens returned, a search billed by records matched —
9
+ the amount isn't known until the backend responds. Set meter values from code at
10
+ runtime with the `MonetizationInboundPolicy` static methods.
11
+
12
+ ## The runtime metering methods
13
+
14
+ | Method | What it does |
15
+ | ---------------------------- | ----------------------------------------------------------------------- |
16
+ | `setMeters(context, meters)` | Replaces the runtime meter map, overriding matching static keys |
17
+ | `addMeters(context, meters)` | Adds to the runtime meter map, accumulating with static and prior calls |
18
+ | `getMeters(context)` | Returns the current runtime meter map |
19
+
20
+ Call them from a custom policy or handler. Because the values usually come from
21
+ the response, the most common place is a custom outbound policy.
22
+
23
+ ```ts
24
+ import {
25
+ MonetizationInboundPolicy,
26
+ ZuploContext,
27
+ ZuploRequest,
28
+ } from "@zuplo/runtime";
29
+
30
+ // In a custom outbound policy, set meters based on the response
31
+ export default async function (
32
+ response: Response,
33
+ request: ZuploRequest,
34
+ context: ZuploContext,
35
+ ) {
36
+ if (!response.ok) {
37
+ return response;
38
+ }
39
+
40
+ // Reading the body consumes it, so rebuild the response afterward
41
+ const body = (await response.json()) as {
42
+ usage?: { total_tokens?: number };
43
+ };
44
+ const tokens = body.usage?.total_tokens ?? 0;
45
+
46
+ MonetizationInboundPolicy.setMeters(context, { tokens_used: tokens });
47
+
48
+ return new Response(JSON.stringify(body), {
49
+ status: response.status,
50
+ headers: response.headers,
51
+ });
52
+ }
53
+ ```
54
+
55
+ Use `addMeters` to add to existing meter values rather than replacing them:
56
+
57
+ ```ts
58
+ MonetizationInboundPolicy.addMeters(context, {
59
+ api_credits: creditsConsumed,
60
+ });
61
+ ```
62
+
63
+ Read the current runtime meter values at any point:
64
+
65
+ ```ts
66
+ const meters = MonetizationInboundPolicy.getMeters(context);
67
+ // { tokens_used: 150 }
68
+ ```
69
+
70
+ ## How meter values are merged
71
+
72
+ The final metering hook combines static and runtime values before sending usage:
73
+
74
+ - `options.meters` provides the static base values.
75
+ - `setMeters` replaces the runtime meter map, overriding matching static keys.
76
+ - `addMeters` accumulates into the runtime meter map, then combines additively
77
+ with static values.
78
+ - When both the static and runtime maps are empty, the policy skips metering.
79
+
80
+ For a meter key like `api` with `options.meters.api = 1`:
81
+
82
+ - `setMeters(context, { api: 50 })` sends `api: 50` (replaces the static value).
83
+ - `addMeters(context, { api: 50 })` sends `api: 51` (adds to the static value).
84
+
85
+ The policy reports usage only for the status codes set by
86
+ [`meterOnStatusCodes`](./monetization-policy.md#meteronstatuscodes), so a failed
87
+ backend response costs the caller nothing.
88
+
89
+ ## Enforcing quotas on runtime meters
90
+
91
+ Runtime meters set from an outbound policy run _after_ the response, so they
92
+ can't block the current request on their own. To enforce a quota on a value you
93
+ meter at runtime, declare the meter statically with a value of `0` — the policy
94
+ checks the entitlement up front without double-counting. See
95
+ [Block on a response-derived meter](./programmatic-monetization.md#block-on-a-response-derived-meter).
96
+
97
+ ## Next steps
98
+
99
+ - [Programmatic Monetization](./programmatic-monetization.md) — gate operations
100
+ by plan and enforce quotas on runtime meters.
101
+ - [Reading Subscription Data](./subscription-data.md) — inspect the plan and
102
+ entitlements in code.
103
+ - [Monetization Policy Reference](./monetization-policy.md) — every policy
104
+ configuration option.
105
+ - [Meters](./meters.mdx) — defining the meters you increment.
@@ -85,22 +85,25 @@ pricing, wire up Stripe, and configure your gateway to enforce quotas.
85
85
 
86
86
  ## Documentation map
87
87
 
88
- | Document | What it covers |
89
- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
90
- | [Quickstart](./monetization/quickstart.md) | End-to-end setup in 30 minutes |
91
- | [Meters](./monetization/meters.mdx) | How meters track usage dimensions |
92
- | [Features](./monetization/features.mdx) | Connecting meters to your product catalog |
93
- | [Plans](./monetization/plans.mdx) | Plan structure, phases, lifecycle, and publishing |
94
- | [Rate Cards](./monetization/rate-cards.mdx) | Pricing and entitlements within plans |
95
- | [Pricing Models](./monetization/pricing-models.mdx) | Flat, per-unit, tiered, volume, and package pricing |
96
- | [Billing Models Guide](./monetization/billing-models.md) | Choosing the right pricing strategy for your business |
97
- | [Stripe Integration](./monetization/stripe-integration.md) | Connecting Stripe and managing payments |
98
- | [Developer Portal Setup](./monetization/developer-portal.md) | Configuring the self-serve portal for your customers |
99
- | [Monetization Policy Reference](./monetization/monetization-policy.md) | Configuring the `MonetizationInboundPolicy` per-route |
100
- | [API Access](./monetization/api-access.mdx) | Authentication, buckets, bucket configuration, Stripe setup APIs, and reference links |
101
- | [Subscription Lifecycle](./monetization/subscription-lifecycle.md) | Managing trials, upgrades, downgrades, and cancellations |
102
- | [Private Plans](./monetization/private-plans.md) | Invite-only plans for specific users |
103
- | [Tax Collection](./monetization/tax-collection.md) | Enabling VAT, sales tax, or GST via Stripe Tax |
104
- | [Going to Production](./monetization/going-to-production.mdx) | Pre-launch checklist, Stripe live mode, and known limitations |
105
- | [Plan Examples](./monetization/plan-examples.mdx) | Real-world plan configurations |
106
- | [Troubleshooting](./monetization/troubleshooting.md) | Common issues, debugging, and FAQ |
88
+ | Document | What it covers |
89
+ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
90
+ | [Quickstart](./monetization/quickstart.md) | End-to-end setup in 30 minutes |
91
+ | [Meters](./monetization/meters.mdx) | How meters track usage dimensions |
92
+ | [Features](./monetization/features.mdx) | Connecting meters to your product catalog |
93
+ | [Plans](./monetization/plans.mdx) | Plan structure, phases, lifecycle, and publishing |
94
+ | [Rate Cards](./monetization/rate-cards.mdx) | Pricing and entitlements within plans |
95
+ | [Pricing Models](./monetization/pricing-models.mdx) | Flat, per-unit, tiered, volume, and package pricing |
96
+ | [Billing Models Guide](./monetization/billing-models.md) | Choosing the right pricing strategy for your business |
97
+ | [Stripe Integration](./monetization/stripe-integration.md) | Connecting Stripe and managing payments |
98
+ | [Developer Portal Setup](./monetization/developer-portal.md) | Configuring the self-serve portal for your customers |
99
+ | [Monetization Policy Reference](./monetization/monetization-policy.md) | Configuring the `MonetizationInboundPolicy` per-route |
100
+ | [Subscription Data](./monetization/subscription-data.md) | Reading the active subscription plan, entitlements, payment status in code |
101
+ | [Dynamic Metering](./monetization/dynamic-metering.md) | Setting meter values at runtime with `setMeters`, `addMeters`, and `getMeters` |
102
+ | [Programmatic Monetization](./monetization/programmatic-monetization.md) | Gating operations by plan and metering from the response in code |
103
+ | [API Access](./monetization/api-access.mdx) | Authentication, buckets, bucket configuration, Stripe setup APIs, and reference links |
104
+ | [Subscription Lifecycle](./monetization/subscription-lifecycle.md) | Managing trials, upgrades, downgrades, and cancellations |
105
+ | [Private Plans](./monetization/private-plans.md) | Invite-only plans for specific users |
106
+ | [Tax Collection](./monetization/tax-collection.md) | Enabling VAT, sales tax, or GST via Stripe Tax |
107
+ | [Going to Production](./monetization/going-to-production.mdx) | Pre-launch checklist, Stripe live mode, and known limitations |
108
+ | [Plan Examples](./monetization/plan-examples.mdx) | Real-world plan configurations |
109
+ | [Troubleshooting](./monetization/troubleshooting.md) | Common issues, debugging, and FAQ |
@@ -83,10 +83,10 @@ Track token consumption for AI applications:
83
83
  The meter aggregates events from the gateway; the per-request quantity comes
84
84
  from the `MonetizationInboundPolicy`. Set a fixed cost per request in the
85
85
  policy's `meters` option, or call
86
- [`MonetizationInboundPolicy.setMeters`](./monetization-policy.md#dynamic-metering)
87
- from a custom outbound policy to report a value derived from the response — for
88
- example, the actual token count an LLM returned. An event for a request that
89
- consumed 50 tokens looks like this:
86
+ [`MonetizationInboundPolicy.setMeters`](./dynamic-metering.md) from a custom
87
+ outbound policy to report a value derived from the response — for example, the
88
+ actual token count an LLM returned. An event for a request that consumed 50
89
+ tokens looks like this:
90
90
 
91
91
  ```json
92
92
  {
@@ -8,6 +8,16 @@ every request to a protected route, authenticates the API key, checks the
8
8
  customer's subscription and payment status, enforces quota, meters the request,
9
9
  and allows or blocks access.
10
10
 
11
+ :::tip
12
+
13
+ Working with monetization in code? See
14
+ [Reading Subscription Data](./subscription-data.md) to inspect the plan and
15
+ entitlements, [Dynamic Metering](./dynamic-metering.md) to set meter values at
16
+ runtime, and [Programmatic Monetization](./programmatic-monetization.md) to gate
17
+ operations by plan.
18
+
19
+ :::
20
+
11
21
  ## Basic configuration
12
22
 
13
23
  Add the policy to your `policies.json`:
@@ -68,7 +78,7 @@ If `meters` is omitted, the policy still authenticates the API key and validates
68
78
  the subscription's payment status, but no usage is recorded. If `meters` is
69
79
  provided, it must contain at least one entry — an empty object throws a
70
80
  configuration error. To track usage at runtime instead of from static config,
71
- see [Dynamic metering](#dynamic-metering).
81
+ see the [Dynamic Metering](./dynamic-metering.md) guide.
72
82
 
73
83
  ```json
74
84
  // Increment the api_requests meter by 1 per request
@@ -152,6 +162,13 @@ below it:
152
162
 
153
163
  Set the value to `0` to block requests immediately when payment is overdue.
154
164
 
165
+ :::tip
166
+
167
+ Read the subscription's plan, entitlements, and payment status in your own code
168
+ with [`getSubscriptionData`](./subscription-data.md).
169
+
170
+ :::
171
+
155
172
  ## Multiple policies for different routes
156
173
 
157
174
  Different routes can have different metering configurations. Define multiple
@@ -210,60 +227,12 @@ Apply each to the appropriate routes:
210
227
 
211
228
  ## Dynamic metering
212
229
 
213
- For AI APIs and other variable-cost endpoints, you may need to meter based on
214
- the response for example, counting the number of tokens returned by an LLM.
215
-
216
- Use the `setMeters` and `addMeters` static methods to set meter values
217
- programmatically at runtime from a custom policy or handler:
218
-
219
- ```typescript
220
- import { MonetizationInboundPolicy } from "@zuplo/runtime";
221
-
222
- // In a custom outbound policy, set meters based on the response
223
- export default async function meterTokens(response, request, context) {
224
- if (response.ok) {
225
- const body = await response.json();
226
- const tokens = body.usage?.total_tokens ?? 0;
227
-
228
- MonetizationInboundPolicy.setMeters(context, {
229
- tokens_used: tokens,
230
- });
231
- }
232
- return response;
233
- }
234
- ```
235
-
236
- You can also use `addMeters` to add to existing meter values rather than
237
- replacing them:
238
-
239
- ```typescript
240
- MonetizationInboundPolicy.addMeters(context, {
241
- api_credits: creditsConsumed,
242
- });
243
- ```
244
-
245
- You can also read the current runtime meter values at any point:
246
-
247
- ```typescript
248
- const meters = MonetizationInboundPolicy.getMeters(context);
249
- // { tokens_used: 150 }
250
- ```
251
-
252
- ### How meter values are merged
253
-
254
- The final metering hook combines static and runtime values before usage is sent:
255
-
256
- - `options.meters` provides the static base values
257
- - `setMeters` replaces the current runtime meter map, overriding matching static
258
- keys
259
- - `addMeters` accumulates into the runtime meter map, then combines additively
260
- with static values
261
- - If both static and runtime maps are empty, metering is skipped
262
-
263
- For a meter key like `api` with `options.meters.api = 1`:
264
-
265
- - `setMeters(context, { api: 50 })` sends `api: 50` (replaces static value)
266
- - `addMeters(context, { api: 50 })` sends `api: 51` (adds to static value)
230
+ For variable-cost endpoints billing by tokens returned, records processed, or
231
+ any value computed at runtime set meter values from code with `setMeters`,
232
+ `addMeters`, and `getMeters` instead of static config. See the
233
+ [Dynamic Metering](./dynamic-metering.md) guide for the full API and merge
234
+ rules, and [Programmatic Monetization](./programmatic-monetization.md) for
235
+ gating operations and enforcing quotas on runtime meters.
267
236
 
268
237
  ## Error responses
269
238
 
@@ -320,3 +289,12 @@ If you still want per-second or per-minute rate limiting on top of monthly
320
289
  quotas, add a standalone rate-limiting policy after the monetization policy.
321
290
  These serve different purposes: monetization enforces billing quotas, while rate
322
291
  limiting protects against traffic spikes.
292
+
293
+ ## Related guides
294
+
295
+ - [Reading Subscription Data](./subscription-data.md) — inspect the plan,
296
+ entitlements, and payment status in code.
297
+ - [Dynamic Metering](./dynamic-metering.md) — set meter values at runtime with
298
+ `setMeters`, `addMeters`, and `getMeters`.
299
+ - [Programmatic Monetization](./programmatic-monetization.md) — gate operations
300
+ by plan and enforce quotas on response-derived meters.
@@ -0,0 +1,254 @@
1
+ ---
2
+ title: Programmatic Monetization
3
+ sidebar_label: Programmatic Monetization
4
+ ---
5
+
6
+ Pricing rules often depend on _who's_ calling or _what_ your API returns. A few
7
+ examples:
8
+
9
+ - Restrict bulk export to the Enterprise plan.
10
+ - Offer advanced search only to plans that include it.
11
+ - Bill an AI endpoint by the tokens it returns, and a search endpoint by the
12
+ rows it matches — not a flat amount per call.
13
+
14
+ You can enforce rules and meter usage declaratively with
15
+ [features and entitlements](./features.mdx) on your plans, in code with custom
16
+ policies, or both together. This guide covers the code path, for decisions that
17
+ need runtime logic. It shows two techniques, then combines them:
18
+
19
+ - **[Gate operations by plan](#gate-operations-by-plan)** — read the caller's
20
+ subscription and allow or block the request based on their plan or
21
+ entitlements.
22
+ - **[Meter from the response](#meter-from-the-response)** — compute usage from
23
+ the response body, report it, and enforce a quota on it.
24
+
25
+ Both build on the [`MonetizationInboundPolicy`](./monetization-policy.md): the
26
+ [subscription data](./subscription-data.md) it exposes and its
27
+ [dynamic metering](./dynamic-metering.md) methods. Add that policy to your
28
+ monetized routes first.
29
+
30
+ ## Gate operations by plan
31
+
32
+ To make an operation available on some plans but not others, read the
33
+ subscription in a custom code inbound policy and block the request when the plan
34
+ doesn't qualify. Place the policy _after_ `monetization-inbound` so the
35
+ subscription data exists on the context.
36
+
37
+ ```ts
38
+ // modules/plan-gate.ts
39
+ import {
40
+ MonetizationInboundPolicy,
41
+ HttpProblems,
42
+ ZuploContext,
43
+ ZuploRequest,
44
+ } from "@zuplo/runtime";
45
+
46
+ export default async function (request: ZuploRequest, context: ZuploContext) {
47
+ const subscription = MonetizationInboundPolicy.getSubscriptionData(context);
48
+
49
+ if (!subscription) {
50
+ return HttpProblems.forbidden(request, context, {
51
+ detail: "No active subscription",
52
+ });
53
+ }
54
+
55
+ // Bulk export is an Enterprise-only operation
56
+ if (subscription.plan.key !== "enterprise") {
57
+ return HttpProblems.forbidden(request, context, {
58
+ detail: "Bulk export requires the Enterprise plan",
59
+ });
60
+ }
61
+
62
+ return request;
63
+ }
64
+ ```
65
+
66
+ To gate on a specific feature instead of the whole plan, check its entitlement:
67
+
68
+ ```ts
69
+ const advancedSearch = subscription.entitlements["advanced_search"];
70
+ if (!advancedSearch?.hasAccess) {
71
+ return HttpProblems.forbidden(request, context, {
72
+ detail: "Your plan does not include advanced search",
73
+ });
74
+ }
75
+ ```
76
+
77
+ Register the policy and apply it after `monetization-inbound` on the routes you
78
+ want to protect:
79
+
80
+ ```json
81
+ // config/policies.json
82
+ {
83
+ "name": "plan-gate",
84
+ "policyType": "custom-code-inbound",
85
+ "handler": {
86
+ "export": "default",
87
+ "module": "$import(./modules/plan-gate)"
88
+ }
89
+ }
90
+ ```
91
+
92
+ ```json
93
+ // On the route
94
+ {
95
+ "x-zuplo-route": {
96
+ "policies": {
97
+ "inbound": ["monetization-inbound", "plan-gate"]
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Meter from the response
104
+
105
+ For variable-cost endpoints, meter a request by something you only know once the
106
+ backend responds — the number of records returned, items processed, or tokens
107
+ generated. Compute the value in a custom code outbound policy and report it with
108
+ `MonetizationInboundPolicy.addMeters`.
109
+
110
+ ```ts
111
+ // modules/count-records.ts
112
+ import {
113
+ MonetizationInboundPolicy,
114
+ ZuploContext,
115
+ ZuploRequest,
116
+ } from "@zuplo/runtime";
117
+
118
+ export default async function (
119
+ response: Response,
120
+ request: ZuploRequest,
121
+ context: ZuploContext,
122
+ ) {
123
+ if (!response.ok) {
124
+ return response;
125
+ }
126
+
127
+ // Reading the body consumes it, so rebuild the response afterward
128
+ const data = (await response.json()) as { records: unknown[] };
129
+ const recordCount = data.records?.length ?? 0;
130
+
131
+ MonetizationInboundPolicy.addMeters(context, { records: recordCount });
132
+
133
+ return new Response(JSON.stringify(data), {
134
+ status: response.status,
135
+ headers: response.headers,
136
+ });
137
+ }
138
+ ```
139
+
140
+ `addMeters` accumulates with any static `meters` and earlier calls; use
141
+ `setMeters` to replace the runtime value instead. The policy sends the combined
142
+ total once the response goes out, and only for the configured status codes. See
143
+ [Dynamic Metering](./dynamic-metering.md) for the full API and merge rules.
144
+
145
+ ## Block on a response-derived meter
146
+
147
+ `addMeters` runs in an outbound policy — it sees the response only after the
148
+ handler returns. By then it's too late to block the current request, and because
149
+ the meter never appeared in the policy's static config, the inbound quota check
150
+ never ran for it. A caller who is already over their limit still gets through.
151
+
152
+ To enforce the quota, declare the meter in the policy's `meters` option with a
153
+ value of **`0`**:
154
+
155
+ ```json
156
+ // config/policies.json
157
+ {
158
+ "name": "monetization-inbound",
159
+ "policyType": "monetization-inbound",
160
+ "handler": {
161
+ "export": "MonetizationInboundPolicy",
162
+ "module": "$import(@zuplo/runtime)",
163
+ "options": {
164
+ "meters": { "records": 0 }
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ This does two things:
171
+
172
+ 1. **Enforces the quota up front.** At request time the policy checks the
173
+ `records` entitlement and returns `403 Forbidden` when the balance has run
174
+ out — before the request reaches your backend.
175
+ 2. **Avoids double-counting.** The static `0` contributes nothing to the total.
176
+ Runtime values add to the static base (`0 + n = n`), so `addMeters` reports
177
+ the exact amount to bill.
178
+
179
+ :::note
180
+
181
+ The quota check runs at the start of each request and lets through any caller
182
+ who still has balance; the policy charges usage after the response. So a request
183
+ whose cost exceeds the remaining balance still completes, the balance goes
184
+ negative, and the policy blocks the next request. An increment of `1` enforces
185
+ the quota exactly — a caller overshoots only when a single request meters more
186
+ than `1` against a partial balance.
187
+
188
+ :::
189
+
190
+ ## Putting it together
191
+
192
+ A complete setup for a metered, plan-gated, response-counted route:
193
+
194
+ ```json
195
+ // config/policies.json
196
+ {
197
+ "policies": [
198
+ {
199
+ "name": "monetization-inbound",
200
+ "policyType": "monetization-inbound",
201
+ "handler": {
202
+ "export": "MonetizationInboundPolicy",
203
+ "module": "$import(@zuplo/runtime)",
204
+ "options": {
205
+ "meters": { "records": 0 }
206
+ }
207
+ }
208
+ },
209
+ {
210
+ "name": "plan-gate",
211
+ "policyType": "custom-code-inbound",
212
+ "handler": {
213
+ "export": "default",
214
+ "module": "$import(./modules/plan-gate)"
215
+ }
216
+ },
217
+ {
218
+ "name": "count-records",
219
+ "policyType": "custom-code-outbound",
220
+ "handler": {
221
+ "export": "default",
222
+ "module": "$import(./modules/count-records)"
223
+ }
224
+ }
225
+ ]
226
+ }
227
+ ```
228
+
229
+ ```json
230
+ // On the route
231
+ {
232
+ "x-zuplo-route": {
233
+ "policies": {
234
+ "inbound": ["monetization-inbound", "plan-gate"],
235
+ "outbound": ["count-records"]
236
+ }
237
+ }
238
+ }
239
+ ```
240
+
241
+ The request flows through the monetization policy (authenticates, checks the
242
+ `records` quota, blocks if the quota has run out), then the plan gate (allows
243
+ the operation only on qualifying plans), then your handler, then the outbound
244
+ policy (counts the records and reports them with `addMeters`).
245
+
246
+ ## Next steps
247
+
248
+ - [Reading Subscription Data](./subscription-data.md) — the full subscription
249
+ object you can read in code.
250
+ - [Dynamic Metering](./dynamic-metering.md) — the full `setMeters` / `addMeters`
251
+ / `getMeters` API and how static and runtime values merge.
252
+ - [Monetization Policy Reference](./monetization-policy.md) — every
253
+ configuration option.
254
+ - [Meters](./meters.mdx) — defining the meters your policies increment.