zuplo 6.70.69 → 6.70.71

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.
Files changed (93) hide show
  1. package/docs/ai-gateway/getting-started.mdx +14 -9
  2. package/docs/ai-gateway/integrations/ai-sdk.mdx +17 -0
  3. package/docs/ai-gateway/introduction.mdx +12 -10
  4. package/docs/ai-gateway/providers.mdx +2 -0
  5. package/docs/analytics/access-and-entitlements.md +71 -0
  6. package/docs/analytics/overview.md +63 -0
  7. package/docs/analytics/reference/metrics-glossary.md +105 -0
  8. package/docs/analytics/reference/url-parameters.md +66 -0
  9. package/docs/analytics/shared-controls.md +121 -0
  10. package/docs/analytics/tabs/agents.md +88 -0
  11. package/docs/analytics/tabs/consumers.md +73 -0
  12. package/docs/analytics/tabs/graphql.md +77 -0
  13. package/docs/analytics/tabs/mcp.md +80 -0
  14. package/docs/analytics/tabs/origins.md +82 -0
  15. package/docs/analytics/tabs/requests.md +96 -0
  16. package/docs/articles/api-key-buckets.mdx +4 -2
  17. package/docs/articles/archiving-requests-to-storage.mdx +4 -4
  18. package/docs/articles/branch-based-deployments.mdx +10 -8
  19. package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
  20. package/docs/articles/ci-cd-github/cleanup-on-branch-delete.mdx +52 -31
  21. package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
  22. package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
  23. package/docs/articles/ci-cd-github/pr-preview-environments.mdx +53 -10
  24. package/docs/articles/custom-ci-cd-azure.mdx +1 -1
  25. package/docs/articles/custom-ci-cd-bitbucket.mdx +1 -1
  26. package/docs/articles/custom-ci-cd-circleci.mdx +1 -1
  27. package/docs/articles/custom-ci-cd-github.mdx +12 -3
  28. package/docs/articles/custom-ci-cd-gitlab.mdx +1 -1
  29. package/docs/articles/graphql.mdx +276 -0
  30. package/docs/articles/monetization/api-access.mdx +184 -0
  31. package/docs/articles/monetization/meters.mdx +4 -4
  32. package/docs/articles/monetization/monetization-policy.md +4 -1
  33. package/docs/articles/monetization/private-plans.md +3 -4
  34. package/docs/articles/monetization/stripe-integration.md +9 -0
  35. package/docs/articles/monetization/subscription-lifecycle.md +12 -11
  36. package/docs/articles/monorepo-deployment.mdx +37 -5
  37. package/docs/articles/opentelemetry.mdx +5 -2
  38. package/docs/articles/securing-the-gateway-with-client-mtls.mdx +68 -43
  39. package/docs/articles/step-1-setup-basic-gateway.mdx +1 -3
  40. package/docs/articles/step-2-add-rate-limiting.mdx +1 -1
  41. package/docs/articles/testing.mdx +1 -1
  42. package/docs/articles/troubleshooting.md +7 -3
  43. package/docs/articles/waf-ddos-akamai.md +35 -16
  44. package/docs/articles/waf-ddos-aws-waf-shield.mdx +35 -16
  45. package/docs/articles/waf-ddos-fastly.mdx +10 -7
  46. package/docs/cli/deploy.mdx +44 -9
  47. package/docs/cli/deploy.partial.mdx +44 -9
  48. package/docs/concepts/api-keys.md +2 -2
  49. package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
  50. package/docs/dev-portal/zudoku/components/sidecar-box.mdx +131 -0
  51. package/docs/dev-portal/zudoku/configuration/api-catalog.md +62 -42
  52. package/docs/dev-portal/zudoku/configuration/api-reference.md +5 -4
  53. package/docs/dev-portal/zudoku/configuration/navigation.mdx +70 -7
  54. package/docs/dev-portal/zudoku/configuration/search.md +36 -0
  55. package/docs/dev-portal/zudoku/configuration/site.md +38 -0
  56. package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
  57. package/docs/errors/rate-limit-exceeded.mdx +30 -3
  58. package/docs/guides/canary-routing-for-employees.mdx +103 -39
  59. package/docs/guides/modify-openapi-paths.mdx +3 -3
  60. package/docs/handlers/legacy-dev-portal-handler.mdx +1 -1
  61. package/docs/handlers/mcp-server.mdx +13 -11
  62. package/docs/handlers/url-forward.mdx +5 -1
  63. package/docs/handlers/url-rewrite.mdx +7 -2
  64. package/docs/handlers/websocket-handler.mdx +5 -1
  65. package/docs/mcp-gateway/observability/logging.mdx +19 -12
  66. package/docs/mcp-server/resources.mdx +27 -15
  67. package/docs/mcp-server/testing.mdx +0 -2
  68. package/docs/policies/_index.md +2 -0
  69. package/docs/policies/archive-request-azure-storage-inbound/doc.md +1 -1
  70. package/docs/policies/archive-response-azure-storage-outbound/doc.md +1 -1
  71. package/docs/policies/data-loss-prevention-inbound/doc.md +116 -0
  72. package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
  73. package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
  74. package/docs/policies/data-loss-prevention-outbound/doc.md +116 -0
  75. package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
  76. package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
  77. package/docs/policies/ip-restriction-inbound/policy.ts +1 -1
  78. package/docs/programmable-api/background-dispatcher.mdx +6 -8
  79. package/docs/programmable-api/http-problems.mdx +0 -18
  80. package/docs/programmable-api/jwt-service-plugin.mdx +131 -109
  81. package/docs/programmable-api/runtime-behaviors.mdx +4 -2
  82. package/docs/programmable-api/streaming-zone-cache.mdx +4 -6
  83. package/docs/programmable-api/web-crypto-apis.mdx +10 -6
  84. package/docs/programmable-api/zone-cache.mdx +1 -1
  85. package/docs/rate-limiting/combining-policies.mdx +293 -0
  86. package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
  87. package/docs/rate-limiting/getting-started.mdx +339 -0
  88. package/docs/rate-limiting/how-it-works.md +225 -0
  89. package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
  90. package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -28
  91. package/package.json +4 -4
  92. package/docs/concepts/rate-limiting.md +0 -246
  93. package/docs/errors/get-head-body-error.mdx +0 -41
@@ -64,7 +64,9 @@ jobs:
64
64
  run: npm install
65
65
 
66
66
  - name: Deploy to Zuplo
67
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
67
+ run:
68
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "${{
69
+ github.ref_name }}"
68
70
  ```
69
71
 
70
72
  This workflow:
@@ -6,6 +6,19 @@ sidebar_label: PR Preview Environments
6
6
  Give every pull request its own Zuplo environment. Reviewers can test changes
7
7
  against a live API, and environments clean up automatically when PRs close.
8
8
 
9
+ :::caution{title="Pass --environment on pull_request events"}
10
+
11
+ Workflows triggered by `pull_request` check out the pull request **merge ref**
12
+ (`refs/pull/<number>/merge`), not your branch. Without `--environment`, the
13
+ Zuplo CLI derives the environment name from that ref and creates an environment
14
+ named `pull/<number>/merge` instead of one named after your branch. If anything
15
+ else deploys the same branch — a `push`-triggered workflow, the GitHub
16
+ integration, or a local `zuplo deploy` — you end up with two environments and
17
+ two different URLs for the same branch. Always pass `--environment` with the
18
+ source branch name (`github.head_ref`).
19
+
20
+ :::
21
+
9
22
  ```yaml title=".github/workflows/pr-workflow.yaml"
10
23
  name: PR Workflow
11
24
 
@@ -13,6 +26,12 @@ on:
13
26
  pull_request:
14
27
  types: [opened, synchronize, reopened, closed]
15
28
 
29
+ # Runs for the same branch share one queue, so rapid pushes don't race
30
+ # concurrent deploys into the same environment
31
+ concurrency:
32
+ group: zuplo-preview-${{ github.head_ref }}
33
+ cancel-in-progress: true
34
+
16
35
  jobs:
17
36
  deploy-and-test:
18
37
  # Run on PR open/update, not on close
@@ -34,8 +53,12 @@ jobs:
34
53
  - name: Deploy to Zuplo
35
54
  id: deploy
36
55
  shell: bash
56
+ env:
57
+ # The PR's source branch — not the pull/<number>/merge ref that
58
+ # pull_request events check out
59
+ ENVIRONMENT: ${{ github.head_ref }}
37
60
  run: |
38
- OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
61
+ OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1)
39
62
  echo "$OUTPUT"
40
63
  DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
41
64
  echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
@@ -71,15 +94,26 @@ jobs:
71
94
  - name: Install dependencies
72
95
  run: npm install
73
96
 
97
+ - name: Get deployment URL
98
+ id: get-url
99
+ uses: actions/github-script@v7
100
+ with:
101
+ script: |
102
+ const comments = await github.rest.issues.listComments({
103
+ issue_number: context.issue.number,
104
+ owner: context.repo.owner,
105
+ repo: context.repo.repo,
106
+ });
107
+ const match = comments.data
108
+ .map((c) => c.body.match(/Deployed to: (https:\/\/\S+)/))
109
+ .find(Boolean);
110
+ core.setOutput("url", match ? match[1] : "");
111
+
74
112
  - name: Delete environment
113
+ if: steps.get-url.outputs.url != ''
75
114
  run: |
76
- # Environment name is based on branch name
77
- BRANCH_NAME="${{ github.head_ref }}"
78
- # Convert slashes to hyphens (Zuplo convention)
79
- ENV_NAME="${BRANCH_NAME//\//-}"
80
-
81
115
  npx zuplo delete \
82
- --environment "$ENV_NAME" \
116
+ --url "${{ steps.get-url.outputs.url }}" \
83
117
  --api-key "$ZUPLO_API_KEY" \
84
118
  --wait
85
119
  ```
@@ -92,12 +126,21 @@ This workflow:
92
126
 
93
127
  ## How It Works
94
128
 
95
- - The environment name comes from the branch name (`feature/auth` becomes
96
- `feature-auth`)
97
- - Each push to the PR updates the same environment
129
+ - The deploy step passes `--environment` with the PR's source branch name
130
+ (`github.head_ref`), so the environment is named after the branch — the same
131
+ name the [GitHub integration](../source-control-setup-github.mdx) would use
132
+ - Each push to the PR updates the same environment, so the environment URL stays
133
+ stable for the life of the branch
98
134
  - Closing the PR (merge or abandon) triggers cleanup
99
135
  - The PR comment lets reviewers quickly access the preview
100
136
 
137
+ A stable environment URL matters whenever an external system references it
138
+ exactly — an OIDC token audience, a webhook registration, or an IP/URL
139
+ allowlist. Capture the URL from the deploy output rather than constructing it
140
+ from the branch name: the URL hostname uses a normalized, truncated form of the
141
+ environment name plus a unique identifier (see
142
+ [Branch-Based Deployments](../branch-based-deployments.mdx)).
143
+
101
144
  ## Next Steps
102
145
 
103
146
  - Add [automatic cleanup on branch delete](./cleanup-on-branch-delete.mdx) as a
@@ -38,7 +38,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
38
38
  npx zuplo test --endpoint https://your-env.zuplo.dev
39
39
 
40
40
  # Clean up environments you no longer need
41
- npx zuplo delete --environment pr-123 --api-key "$ZUPLO_API_KEY"
41
+ npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
42
42
 
43
43
  # Test locally before deploying
44
44
  npx zuplo dev # starts local server on port 9000
@@ -37,7 +37,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
37
37
  npx zuplo test --endpoint https://your-env.zuplo.dev
38
38
 
39
39
  # Clean up environments you no longer need
40
- npx zuplo delete --environment pr-123 --api-key "$ZUPLO_API_KEY"
40
+ npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
41
41
 
42
42
  # Test locally before deploying
43
43
  npx zuplo dev # starts local server on port 9000
@@ -36,7 +36,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
36
36
  npx zuplo test --endpoint https://your-env.zuplo.dev
37
37
 
38
38
  # Clean up environments you no longer need
39
- npx zuplo delete --environment pr-123 --api-key "$ZUPLO_API_KEY"
39
+ npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
40
40
 
41
41
  # Test locally before deploying
42
42
  npx zuplo dev # starts local server on port 9000
@@ -47,14 +47,14 @@ The Zuplo CLI handles deployment, testing, and environment management. Your
47
47
  GitHub Actions workflow orchestrates when these happen:
48
48
 
49
49
  ```bash
50
- # Deploy to Zuplo (environment name from branch or --environment flag)
51
- npx zuplo deploy --api-key "$ZUPLO_API_KEY"
50
+ # Deploy to Zuplo (pass --environment with the branch name see below)
51
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$BRANCH_NAME"
52
52
 
53
53
  # Run tests against any Zuplo environment
54
54
  npx zuplo test --endpoint https://your-env.zuplo.dev
55
55
 
56
56
  # Clean up environments you no longer need
57
- npx zuplo delete --environment pr-123 --api-key "$ZUPLO_API_KEY"
57
+ npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
58
58
 
59
59
  # Test locally before deploying
60
60
  npx zuplo dev # starts local server on port 9000
@@ -64,6 +64,15 @@ Capture the deployment URL from the deploy command output to pass to subsequent
64
64
  test steps. The CLI outputs `Deployed to https://...` which you can parse and
65
65
  use throughout your workflow.
66
66
 
67
+ Always pass `--environment` with the branch name when deploying from GitHub
68
+ Actions, so that every workflow run for a branch updates the same environment.
69
+ Without it, the CLI infers the environment name from the checked-out git ref —
70
+ and on `pull_request` events that's the PR merge ref
71
+ (`refs/pull/<number>/merge`), not your branch, which silently creates a second
72
+ environment with a different URL. The expression
73
+ `${{ github.head_ref || github.ref_name }}` resolves to the branch name on both
74
+ `pull_request` and `push` events.
75
+
67
76
  ## Prerequisites
68
77
 
69
78
  1. **Disconnect the GitHub integration** — Custom CI/CD replaces automatic
@@ -36,7 +36,7 @@ npx zuplo deploy --api-key "$ZUPLO_API_KEY"
36
36
  npx zuplo test --endpoint https://your-env.zuplo.dev
37
37
 
38
38
  # Clean up environments you no longer need
39
- npx zuplo delete --environment pr-123 --api-key "$ZUPLO_API_KEY"
39
+ npx zuplo delete --url "https://my-api-pr-123-d3vp01.zuplo.app" --api-key "$ZUPLO_API_KEY"
40
40
 
41
41
  # Test locally before deploying
42
42
  npx zuplo dev # starts local server on port 9000
@@ -0,0 +1,276 @@
1
+ ---
2
+ title: GraphQL on Zuplo
3
+ sidebar_label: GraphQL
4
+ description:
5
+ Proxy a GraphQL API through your Zuplo gateway and publish a browsable schema
6
+ reference with a playground in your Dev Portal.
7
+ tags:
8
+ - backends
9
+ ---
10
+
11
+ Zuplo fronts GraphQL APIs the same way it fronts REST: requests pass through a
12
+ gateway route — with whatever authentication, rate limiting, and
13
+ GraphQL-specific policies you attach — and your Dev Portal documents the schema.
14
+ This guide covers both halves: adding a GraphQL endpoint to your gateway routes,
15
+ then rendering a schema reference and playground in the Dev Portal with
16
+ `@zudoku/plugin-graphql`.
17
+
18
+ ## Prerequisites
19
+
20
+ - A Zuplo project
21
+ - An upstream GraphQL API to proxy
22
+ - For the Dev Portal section: your project's
23
+ [Dev Portal](../dev-portal/introduction.mdx) lives in its `docs/` folder. The
24
+ plugin requires the `zudoku` package version `0.80.1` or newer — check the
25
+ version in `docs/package.json` and
26
+ [update the Dev Portal](../dev-portal/updating.mdx) if yours is older.
27
+
28
+ ## Add a GraphQL endpoint to your gateway
29
+
30
+ A GraphQL API serves every query and mutation from a single endpoint, so the
31
+ gateway route uses the [URL Rewrite handler](../handlers/url-rewrite.mdx) —
32
+ every request goes to exactly the upstream URL — rather than URL Forward, which
33
+ appends the incoming path.
34
+
35
+ ### Add a new GraphQL route
36
+
37
+ <Stepper>
38
+
39
+ 1. **Open the Route Designer**
40
+
41
+ In the Zuplo Portal, open the **Code** tab and click **routes.oas.json**.
42
+ This opens the Route Designer, which lists your project's existing routes and
43
+ an **Add** menu.
44
+
45
+ 2. **Add the endpoint**
46
+
47
+ Click **Add** and select **GraphQL Endpoint**. This creates a `POST /graphql`
48
+ route preconfigured with the URL Rewrite handler and a demo upstream. If
49
+ `/graphql` is already taken, the Portal appends a short suffix (for example
50
+ `/graphql-b891`) — edit the path to whatever you prefer. GraphQL routes show
51
+ a **GraphQL** badge in the route list instead of an HTTP method.
52
+
53
+ 3. **Point it at your upstream**
54
+
55
+ Expand the new route. Its handler is preset to **URL Rewrite** in the
56
+ **Request Handler** drop-down; in the URL text box below it, replace the demo
57
+ URL with your GraphQL API's endpoint, for example
58
+ `https://api.example.com/graphql`. To use a different backend per
59
+ environment, reference an [environment variable](./environment-variables.mdx)
60
+ such as `${env.GRAPHQL_API_URL}`.
61
+
62
+ 4. **Save**
63
+
64
+ Save your changes. Your gateway now proxies GraphQL requests at `/graphql`.
65
+
66
+ </Stepper>
67
+
68
+ ### Mark an existing route as GraphQL
69
+
70
+ Already proxying a GraphQL API through a regular route? Open the route in the
71
+ Route Designer, click the **⋯** menu at the end of the route's options row (next
72
+ to the CORS and docs toggles), and check **Mark as GraphQL**. This tags the
73
+ route as a GraphQL endpoint without changing its handler or policies.
74
+
75
+ ### The resulting route configuration
76
+
77
+ Both paths produce a route in `routes.oas.json` with the `x-graphql` extension
78
+ set:
79
+
80
+ ```json title="config/routes.oas.json"
81
+ {
82
+ "paths": {
83
+ "/graphql": {
84
+ "post": {
85
+ "summary": "GraphQL Endpoint",
86
+ "x-graphql": true,
87
+ "x-zuplo-route": {
88
+ "corsPolicy": "none",
89
+ "handler": {
90
+ "export": "urlRewriteHandler",
91
+ "module": "$import(@zuplo/runtime)",
92
+ "options": {
93
+ "rewritePattern": "https://api.example.com/graphql"
94
+ }
95
+ },
96
+ "policies": {
97
+ "inbound": []
98
+ }
99
+ },
100
+ "operationId": "graphql-1a2bc3d4"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ :::tip{title="Secure the endpoint"}
108
+
109
+ GraphQL has its own attack surface — deeply nested queries, expensive
110
+ operations, and schema introspection. Zuplo ships policies for all three. See
111
+ [Secure your GraphQL API](./graphql-security.mdx) to add complexity limits and
112
+ disable introspection in production.
113
+
114
+ :::
115
+
116
+ ## Document the API in your Dev Portal
117
+
118
+ The `@zudoku/plugin-graphql` package renders GraphQL APIs in your Dev Portal
119
+ from a schema. Each plugin instance produces a browsable type reference
120
+ (queries, mutations, objects, enums, and so on) plus a playground, generated
121
+ straight from your schema.
122
+
123
+ :::caution{title="Beta"}
124
+
125
+ The GraphQL plugin is in beta. During the beta it isn't available in your
126
+ project's working copy — it only works in projects
127
+ [connected to source control](./source-control.mdx).
128
+
129
+ :::
130
+
131
+ <Stepper>
132
+
133
+ 1. **Install the plugin**
134
+
135
+ In your project's `docs/` folder (where `zudoku.config.tsx` lives), install
136
+ the plugin:
137
+
138
+ ```bash
139
+ npm install @zudoku/plugin-graphql
140
+ ```
141
+
142
+ The plugin requires `zudoku` `0.80.1` or newer.
143
+
144
+ 2. **Add a schema**
145
+
146
+ Drop a GraphQL schema definition language (SDL) file next to your config:
147
+
148
+ ```graphql title="schema.graphql"
149
+ type Query {
150
+ product(id: ID!): Product
151
+ }
152
+
153
+ type Product {
154
+ id: ID!
155
+ name: String!
156
+ price: Float!
157
+ }
158
+ ```
159
+
160
+ Don't have an SDL file handy? Skip this step and introspect a live endpoint
161
+ at build time with `type: "url"` in the next step — the schema is fetched for
162
+ you when the portal builds.
163
+
164
+ 3. **Register the plugin**
165
+
166
+ Import `graphqlPlugin` and add an instance per API. The `path` is where the
167
+ docs mount, and `input` points at your schema:
168
+
169
+ ```tsx title="zudoku.config.tsx"
170
+ import { graphqlPlugin } from "@zudoku/plugin-graphql";
171
+
172
+ const config = {
173
+ plugins: [
174
+ graphqlPlugin({
175
+ type: "file",
176
+ input: "./schema.graphql",
177
+ path: "/graphql/ecommerce",
178
+ options: {
179
+ title: "E-Commerce GraphQL API",
180
+ description: "Products, orders, and customers.",
181
+ playground: {
182
+ endpoint: "https://my-gateway.example.com/graphql",
183
+ },
184
+ },
185
+ }),
186
+ ],
187
+ };
188
+
189
+ export default config;
190
+ ```
191
+
192
+ Set `playground.endpoint` to the gateway route from the first half of this
193
+ guide so readers run real queries through your gateway. With a file-based
194
+ schema the plugin doesn't know where your API lives — leave the endpoint
195
+ unset and the reference pages still render, but the playground asks you to
196
+ configure `options.playground.endpoint` before it can run operations.
197
+
198
+ With `type: "url"`, set `input` to your GraphQL endpoint's URL instead: the
199
+ schema is fetched via introspection when the portal builds, and the
200
+ playground defaults to that same URL. You can register the plugin more than
201
+ once to document several schemas, each with its own `path`.
202
+
203
+ 4. **Link it in the navigation**
204
+
205
+ Point a navigation link at the instance's `path`. Set `stack: true` so the
206
+ API's own pages render as a stacked sub-navigation instead of expanding
207
+ inline:
208
+
209
+ ```tsx title="zudoku.config.tsx"
210
+ navigation: [
211
+ {
212
+ type: "category",
213
+ label: "Documentation",
214
+ items: [
215
+ {
216
+ type: "link",
217
+ label: "E-Commerce API",
218
+ to: "/graphql/ecommerce",
219
+ stack: true,
220
+ },
221
+ ],
222
+ },
223
+ ];
224
+ ```
225
+
226
+ Prefer the API as its own top-level section instead? Drop the link at the top
227
+ level of `navigation` and leave off `stack`. See the
228
+ [navigation reference](../dev-portal/zudoku/configuration/navigation.mdx) for
229
+ all link, category, and stacking options.
230
+
231
+ </Stepper>
232
+
233
+ ### Plugin options
234
+
235
+ All options live under the instance's `options` key:
236
+
237
+ | Option | Type | Description |
238
+ | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
239
+ | `title` | `string` | Display title for the API's overview page. |
240
+ | `description` | `string` | Short description shown on the overview page. |
241
+ | `showDeprecated` | `boolean` | Include deprecated fields and operations in the reference. |
242
+ | `playground.enabled` | `boolean` | Show the playground. Defaults to `true`. |
243
+ | `playground.endpoint` | `string` | URL the playground sends operations to. Defaults to `input` for `type: "url"`. File schemas have no default; the playground prompts for one until set. |
244
+ | `playground.headers` | `object` | Default headers the playground sends with each operation. |
245
+
246
+ ## Verify it worked
247
+
248
+ Open the
249
+ [**Environments**](https://portal.zuplo.com/+/account/project/environments) tab
250
+ in the Zuplo Portal — your build is listed there along with the URLs for both
251
+ your gateway and your Dev Portal. Send a query through the gateway URL:
252
+
253
+ ```bash
254
+ curl https://my-gateway.example.com/graphql \
255
+ -H "Content-Type: application/json" \
256
+ -d '{"query":"{ __typename }"}'
257
+ ```
258
+
259
+ A working route returns a response from your upstream, such as
260
+ `{"data":{"__typename":"Query"}}`.
261
+
262
+ For the Dev Portal, open its URL from the **Environments** tab (or run
263
+ `npm run dev` in your `docs/` folder) and go to the plugin's `path` (for example
264
+ `/graphql/ecommerce`). The overview page lists the schema's types, and the
265
+ playground runs operations against your configured endpoint.
266
+
267
+ ## Next steps
268
+
269
+ - [Secure your GraphQL API](./graphql-security.mdx) — complexity limits and
270
+ introspection policies
271
+ - [Testing GraphQL queries](./testing-graphql.mdx) — test the endpoint from the
272
+ Zuplo Portal or external tools
273
+ - [URL Rewrite handler](../handlers/url-rewrite.mdx) — full reference for the
274
+ handler GraphQL routes use
275
+ - [MCP Server GraphQL endpoints](../mcp-server/graphql.mdx) — expose GraphQL
276
+ queries as MCP tools for AI agents
@@ -52,6 +52,190 @@ curl \
52
52
  --header "Authorization: Bearer $ZAPI_KEY"
53
53
  ```
54
54
 
55
+ ## Bucket monetization configuration
56
+
57
+ Each bucket has an optional `MonetizationConfiguration` that holds bucket-wide
58
+ behavior — multi-subscription support, plan display order, plan-level overrides,
59
+ and the default payment grace period. The configuration is read by the runtime
60
+ and the Developer Portal; it is not stored in OpenMeter.
61
+
62
+ ### Read
63
+
64
+ ```shell
65
+ curl \
66
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
67
+ --header "Authorization: Bearer $ZAPI_KEY"
68
+ ```
69
+
70
+ When no configuration row exists for the bucket, the endpoint returns a default
71
+ body with `multipleSubscriptionsEnabled: false`, an empty `planOrder`, empty
72
+ `planSettings`, and `maxPaymentOverdueDays: 3`.
73
+
74
+ ### Upsert
75
+
76
+ ```shell
77
+ curl \
78
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
79
+ --request PUT \
80
+ --header "Authorization: Bearer $ZAPI_KEY" \
81
+ --header "Content-Type: application/json" \
82
+ --data @- << EOF
83
+ {
84
+ "multipleSubscriptionsEnabled": false,
85
+ "planOrder": ["free", "starter", "pro", "enterprise"],
86
+ "planSettings": {
87
+ "pro": { "visiblePhases": ["default"] }
88
+ },
89
+ "maxPaymentOverdueDays": 7
90
+ }
91
+ EOF
92
+ ```
93
+
94
+ | Field | Type | Description |
95
+ | ------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `multipleSubscriptionsEnabled` | `boolean` | Stored on the bucket. Reserved for future multi-subscription rules; today the Developer Portal create-subscription path enforces a single active subscription per customer regardless of this flag. |
97
+ | `planOrder` | `string[]` | Ordered list of plan keys; drives pricing-page sort and upgrade/downgrade direction during plan changes |
98
+ | `planSettings` | `object` | Per-plan overrides keyed by plan key. The supported sub-key today is `visiblePhases` — an array of phase keys that should appear on the pricing page |
99
+ | `maxPaymentOverdueDays` | `integer` | Bucket-default payment grace period. Must be ≥ 0. Defaults to `3` when not set |
100
+
101
+ The request body must include at least one of these fields. All four fields are
102
+ optional in the request — the upsert preserves any field you don't send.
103
+
104
+ `planOrder` is consumed when a customer changes plans through the Developer
105
+ Portal: a target plan whose index is greater than or equal to the current plan's
106
+ index is treated as an upgrade (immediate timing); a lower index is treated as a
107
+ downgrade (next-billing-cycle timing). Plans not listed in `planOrder` default
108
+ to upgrade timing.
109
+
110
+ `maxPaymentOverdueDays` is the lowest-precedence default for the payment grace
111
+ period. See
112
+ [Subscription and payment validation](./monetization-policy.md#subscription-and-payment-validation)
113
+ for the full precedence chain (customer metadata → plan metadata → bucket
114
+ configuration → built-in default).
115
+
116
+ ### Delete
117
+
118
+ ```shell
119
+ curl \
120
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
121
+ --request DELETE \
122
+ --header "Authorization: Bearer $ZAPI_KEY"
123
+ ```
124
+
125
+ After deletion, GET returns the default body again.
126
+
127
+ ## Stripe setup and billing readiness
128
+
129
+ Most users connect Stripe through the
130
+ [Zuplo Portal](./stripe-integration.md#connecting-your-stripe-account). For
131
+ automated provisioning — CI scripts, infrastructure-as-code, or self-hosted
132
+ control planes — the same flow is available via these API endpoints.
133
+
134
+ ### Install the Stripe app
135
+
136
+ Connect a Stripe account to a bucket and create the default billing profile in
137
+ one call:
138
+
139
+ ```shell
140
+ curl \
141
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe \
142
+ --request POST \
143
+ --header "Authorization: Bearer $ZAPI_KEY" \
144
+ --header "Content-Type: application/json" \
145
+ --data @- << EOF
146
+ {
147
+ "apiKey": "rk_test_...",
148
+ "name": "Stripe Billing Profile",
149
+ "taxEnabled": false,
150
+ "taxEnforced": false,
151
+ "country": "US"
152
+ }
153
+ EOF
154
+ ```
155
+
156
+ The endpoint validates the Stripe key prefix against the bucket's environment:
157
+
158
+ - Working-copy and preview buckets accept `sk_test_*` or `rk_test_*`
159
+ - Production buckets accept `sk_live_*` or `rk_live_*`
160
+
161
+ The response returns the installed `appId`. The endpoint fails with a
162
+ `409 Conflict` if a Stripe app is already installed for the bucket.
163
+
164
+ ### Read the current Stripe setup
165
+
166
+ ```shell
167
+ curl \
168
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe \
169
+ --header "Authorization: Bearer $ZAPI_KEY"
170
+ ```
171
+
172
+ Returns the connected Stripe app summary and the billing profiles linked to it.
173
+
174
+ ### Create an additional billing profile
175
+
176
+ To attach more billing profiles to the same Stripe app:
177
+
178
+ ```shell
179
+ curl \
180
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe/$STRIPE_APP_ID/billing-profile \
181
+ --request POST \
182
+ --header "Authorization: Bearer $ZAPI_KEY" \
183
+ --header "Content-Type: application/json" \
184
+ --data @- << EOF
185
+ {
186
+ "name": "EU Billing Profile",
187
+ "taxEnabled": true,
188
+ "taxEnforced": false,
189
+ "country": "DE"
190
+ }
191
+ EOF
192
+ ```
193
+
194
+ ### Check billing readiness
195
+
196
+ A lightweight check for tooling that gates deploys on Stripe being connected:
197
+
198
+ ```shell
199
+ curl \
200
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/billing-readiness \
201
+ --header "Authorization: Bearer $ZAPI_KEY"
202
+ ```
203
+
204
+ Response:
205
+
206
+ ```json
207
+ {
208
+ "hasStripeApp": true,
209
+ "stripeAppId": "app_01H...",
210
+ "hasDefaultBillingProfile": true,
211
+ "defaultBillingProfileId": "bp_01H..."
212
+ }
213
+ ```
214
+
215
+ Use this in setup wizards to gate the UI on whether Stripe is connected.
216
+
217
+ ### Update a connected app
218
+
219
+ Rotate the Stripe key on an existing app, or update its name and metadata:
220
+
221
+ ```shell
222
+ curl \
223
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/apps/$APP_ID \
224
+ --request PUT \
225
+ --header "Authorization: Bearer $ZAPI_KEY" \
226
+ --header "Content-Type: application/json" \
227
+ --data @- << EOF
228
+ {
229
+ "type": "stripe",
230
+ "name": "Stripe Billing Profile",
231
+ "secretAPIKey": "rk_test_..."
232
+ }
233
+ EOF
234
+ ```
235
+
236
+ The same key-prefix validation applies — a live key is rejected on a
237
+ non-production bucket and vice versa.
238
+
55
239
  ## API Reference
56
240
 
57
241
  For complete API operations, see the API Reference documentation:
@@ -58,10 +58,10 @@ carries the quantity:
58
58
  `subject` identifies _who_ consumed the subscription's entitlements on this
59
59
  request — typically the API key's consumer name, the end-user id, or another
60
60
  stable per-actor identifier. It is **not** the subscription id (use the
61
- `subscription` field for that) and is not used to route billing. Its purpose is
62
- to let you break down usage within a subscription so you can see which key,
63
- user, or agent drove the consumption — a single subscription will commonly emit
64
- events with many different `subject` values. See
61
+ `subscription` field for that) and does not route billing. Its purpose is to let
62
+ you break down usage within a subscription so you can see which key, user, or
63
+ agent drove the consumption — a single subscription will commonly emit events
64
+ with many different `subject` values. See
65
65
  [Monetization Policy](./monetization-policy.md) for how usage is recorded.
66
66
 
67
67
  :::
@@ -145,7 +145,10 @@ below it:
145
145
 
146
146
  1. **Customer metadata** — `zuplo_max_payment_overdue_days` on the customer
147
147
  2. **Plan metadata** — `zuplo_max_payment_overdue_days` on the plan
148
- 3. **Default** — `3` days
148
+ 3. **Bucket configuration** —
149
+ [`maxPaymentOverdueDays`](./api-access.mdx#bucket-monetization-configuration)
150
+ on the bucket's monetization configuration
151
+ 4. **Default** — `3` days
149
152
 
150
153
  Set the value to `0` to block requests immediately when payment is overdue.
151
154