zuplo 6.70.70 → 6.71.0

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 (79) hide show
  1. package/docs/ai-gateway/getting-started.mdx +2 -1
  2. package/docs/ai-gateway/integrations/ai-sdk.mdx +17 -0
  3. package/docs/ai-gateway/introduction.mdx +5 -5
  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 +67 -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 +122 -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 +78 -0
  13. package/docs/analytics/tabs/mcp.md +80 -0
  14. package/docs/analytics/tabs/origins.md +83 -0
  15. package/docs/analytics/tabs/requests.md +97 -0
  16. package/docs/articles/accounts/enterprise-sso.mdx +8 -6
  17. package/docs/articles/api-key-administration.mdx +4 -0
  18. package/docs/articles/bypass-policy-for-testing.mdx +4 -0
  19. package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
  20. package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
  21. package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
  22. package/docs/articles/ci-cd-github/pr-preview-environments.mdx +36 -4
  23. package/docs/articles/custom-ci-cd-github.mdx +11 -2
  24. package/docs/articles/environment-variables.mdx +5 -1
  25. package/docs/articles/environments.mdx +2 -2
  26. package/docs/articles/graphql.mdx +23 -39
  27. package/docs/articles/mcp-quickstart-local.mdx +2 -1
  28. package/docs/articles/mcp-quickstart.mdx +6 -1
  29. package/docs/articles/monetization/api-access.mdx +184 -0
  30. package/docs/articles/monetization/meters.mdx +4 -4
  31. package/docs/articles/monetization/monetization-policy.md +4 -1
  32. package/docs/articles/monetization/private-plans.md +3 -4
  33. package/docs/articles/monetization/stripe-integration.md +9 -0
  34. package/docs/articles/monetization/subscription-lifecycle.md +12 -11
  35. package/docs/articles/monorepo-deployment.mdx +20 -2
  36. package/docs/articles/multiple-auth-policies.mdx +2 -2
  37. package/docs/articles/openapi.mdx +6 -1
  38. package/docs/articles/rename-or-move-project.mdx +4 -0
  39. package/docs/articles/securing-your-backend.mdx +11 -3
  40. package/docs/articles/source-control-setup-github.mdx +4 -0
  41. package/docs/articles/troubleshooting-slow-responses.mdx +2 -2
  42. package/docs/articles/troubleshooting.md +3 -3
  43. package/docs/cli/deploy.mdx +32 -0
  44. package/docs/cli/deploy.partial.mdx +32 -0
  45. package/docs/concepts/api-keys.md +2 -2
  46. package/docs/dedicated/akamai/architecture.mdx +9 -9
  47. package/docs/dev-portal/zudoku/components/browser-window.mdx +94 -0
  48. package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
  49. package/docs/dev-portal/zudoku/components/landing-page.mdx +283 -0
  50. package/docs/dev-portal/zudoku/configuration/search.md +36 -0
  51. package/docs/dev-portal/zudoku/configuration/site.md +38 -0
  52. package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
  53. package/docs/errors/rate-limit-exceeded.mdx +30 -3
  54. package/docs/handlers/system-handlers.mdx +2 -1
  55. package/docs/mcp-gateway/how-to/connect-upstream-api-key.mdx +2 -2
  56. package/docs/mcp-gateway/observability/analytics.mdx +17 -13
  57. package/docs/mcp-gateway/quickstart.mdx +4 -3
  58. package/docs/mcp-gateway/troubleshooting.mdx +4 -4
  59. package/docs/policies/_index.md +3 -0
  60. package/docs/policies/data-loss-prevention-inbound/doc.md +115 -0
  61. package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
  62. package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
  63. package/docs/policies/data-loss-prevention-outbound/doc.md +115 -0
  64. package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
  65. package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
  66. package/docs/policies/graphql-analytics-outbound/doc.md +93 -0
  67. package/docs/policies/graphql-analytics-outbound/intro.md +12 -0
  68. package/docs/policies/graphql-analytics-outbound/schema.json +93 -0
  69. package/docs/programmable-api/background-dispatcher.mdx +6 -8
  70. package/docs/programmable-api/zone-cache.mdx +1 -1
  71. package/docs/programmable-api/zuplo-context.mdx +3 -2
  72. package/docs/rate-limiting/combining-policies.mdx +293 -0
  73. package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
  74. package/docs/rate-limiting/getting-started.mdx +339 -0
  75. package/docs/rate-limiting/how-it-works.md +225 -0
  76. package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
  77. package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -27
  78. package/package.json +4 -4
  79. package/docs/concepts/rate-limiting.md +0 -246
@@ -132,8 +132,10 @@ project's working copy — it only works in projects
132
132
 
133
133
  1. **Install the plugin**
134
134
 
135
- In your project's `docs/` folder (where `zudoku.config.tsx` lives), install
136
- the plugin:
135
+ Check `docs/package.json` first new projects ship with
136
+ `@zudoku/plugin-graphql` already listed in `dependencies`, so there's nothing
137
+ to install. Only older projects need to add it. In your project's `docs/`
138
+ folder (where `zudoku.config.tsx` lives), install the plugin:
137
139
 
138
140
  ```bash
139
141
  npm install @zudoku/plugin-graphql
@@ -141,30 +143,10 @@ project's working copy — it only works in projects
141
143
 
142
144
  The plugin requires `zudoku` `0.80.1` or newer.
143
145
 
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**
146
+ 2. **Register the plugin**
165
147
 
166
148
  Import `graphqlPlugin` and add an instance per API. The `path` is where the
167
- docs mount, and `input` points at your schema:
149
+ docs mount, and `input` points at your GraphQL endpoint:
168
150
 
169
151
  ```tsx title="zudoku.config.tsx"
170
152
  import { graphqlPlugin } from "@zudoku/plugin-graphql";
@@ -172,15 +154,12 @@ project's working copy — it only works in projects
172
154
  const config = {
173
155
  plugins: [
174
156
  graphqlPlugin({
175
- type: "file",
176
- input: "./schema.graphql",
157
+ type: "url",
158
+ input: "https://graphql.example.com/api",
177
159
  path: "/graphql/ecommerce",
178
160
  options: {
179
161
  title: "E-Commerce GraphQL API",
180
162
  description: "Products, orders, and customers.",
181
- playground: {
182
- endpoint: "https://my-gateway.example.com/graphql",
183
- },
184
163
  },
185
164
  }),
186
165
  ],
@@ -189,18 +168,23 @@ project's working copy — it only works in projects
189
168
  export default config;
190
169
  ```
191
170
 
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.
171
+ With `type: "url"`, the schema is fetched via introspection when the portal
172
+ builds, and the playground sends operations to that same URL by default.
173
+ Point `input` at the gateway route from the first half of this guide so
174
+ readers run real queries through your gateway or keep the playground
175
+ separate from the schema source with `options.playground.endpoint`.
176
+
177
+ Have a GraphQL schema definition language (SDL) file instead of a live
178
+ endpoint? Use `type: "file"` and set `input` to the file's path (for example
179
+ `./schema.graphql`). A file-based schema doesn't tell the plugin where your
180
+ API lives, so also set `options.playground.endpoint` — without it the
181
+ reference pages still render, but the playground asks for an endpoint before
182
+ it can run operations.
197
183
 
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`.
184
+ You can register the plugin more than once to document several schemas, each
185
+ with its own `path`.
202
186
 
203
- 4. **Link it in the navigation**
187
+ 3. **Link it in the navigation**
204
188
 
205
189
  Point a navigation link at the instance's `path`. Set `stack: true` so the
206
190
  API's own pages render as a stacked sub-navigation instead of expanding
@@ -39,7 +39,8 @@ If you're not familiar with Zuplo, it's recommended to go through
39
39
 
40
40
  1. Create an **MCP Server**
41
41
 
42
- On your `routes.oas.json` file, choose **Add** and then **MCP Server**.
42
+ On your `routes.oas.json` file, choose **Add** and then **Dynamic OpenAPI to
43
+ MCP Server**.
43
44
 
44
45
  ![Add Route](../../public/media/mcp-quickstart/add-mcp-route.png)
45
46
 
@@ -21,8 +21,12 @@ If you're not familiar with Zuplo, it's recommended to go through the
21
21
  Let's import an OpenAPI document. You can download this one here
22
22
  [todo-openapi.json](https://download-open-api-main-fae215f.d2.zuplo.dev/todo-openapi).
23
23
 
24
+ <ModalScreenshot size="sm">
25
+
24
26
  ![Import OpenAPI](../../public/media/mcp-quickstart/import-openapi.png)
25
27
 
28
+ </ModalScreenshot>
29
+
26
30
  Select the **Code** tab (1), then choose the `routes.oas.json` file (2) and
27
31
  choose **Import OpenAPI** (3).
28
32
 
@@ -49,7 +53,8 @@ If you're not familiar with Zuplo, it's recommended to go through the
49
53
 
50
54
  1. Create an **MCP Server**
51
55
 
52
- On your `routes.oas.json` file, choose **Add** and then **MCP Server** (3)
56
+ On your `routes.oas.json` file, choose **Add** and then **Dynamic OpenAPI to
57
+ MCP Server** (3)
53
58
 
54
59
  ![Add Route](../../public/media/mcp-quickstart/add-mcp-route.png)
55
60
 
@@ -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
 
@@ -99,10 +99,9 @@ Save the returned `id` — you need it to publish and invite users.
99
99
 
100
100
  :::note
101
101
 
102
- The plan `id` is a 26-character ULID (regex
103
- `^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$`), separate from the human-friendly
104
- `key` you set on creation. The publish, invite, and other plan-scoped endpoints
105
- require the `id`, not the `key`.
102
+ The plan `id` is a 26-character ULID. It's distinct from the human-friendly
103
+ `key` field. Use the `id` (not the `key`) when calling `/publish` and
104
+ `/plan-invites`.
106
105
 
107
106
  :::
108
107
 
@@ -45,6 +45,15 @@ specifically Customers, Checkout Sessions, Customer Portal Sessions, Invoices,
45
45
  and Tax Calculations. See
46
46
  [What Zuplo creates in Stripe](#what-zuplo-creates-in-stripe) for the full list.
47
47
 
48
+ :::tip
49
+
50
+ To script the connection — for CI, infrastructure-as-code, or self-hosted
51
+ control planes — use the
52
+ [Stripe setup API endpoints](./api-access.mdx#stripe-setup-and-billing-readiness)
53
+ instead of the Portal flow.
54
+
55
+ :::
56
+
48
57
  ### Test mode vs. live mode
49
58
 
50
59
  Connect with a Stripe **test** key (`sk_test_...`) first to validate your
@@ -67,8 +67,9 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions \
67
67
  -H "Authorization: Bearer {API_KEY}" \
68
68
  -H "Content-Type: application/json" \
69
69
  -d '{
70
- "plan": { "key": "pro" },
71
- "customerId": "01J9ZX2A8R0K8H6VG2C1A0K3WP"
70
+ "plan": { "key": "pro", "version": 1 },
71
+ "customerKey": "user_external_id",
72
+ "timing": "immediate"
72
73
  }'
73
74
  ```
74
75
 
@@ -216,14 +217,15 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscri
216
217
  -H "Content-Type: application/json" \
217
218
  -d '{
218
219
  "timing": "immediate",
219
- "plan": { "key": "enterprise" }
220
+ "plan": { "key": "enterprise", "version": 1 }
220
221
  }'
221
222
  ```
222
223
 
223
224
  `timing` accepts `"immediate"`, `"next_billing_cycle"`, or an RFC 3339
224
225
  timestamp. Optional fields include `startingPhase`, `name`, `description`,
225
- `metadata`, `alignment`, and `billingAnchor`. To preview the proration credit
226
- before committing, call
226
+ `metadata`, `alignment`, and `billingAnchor`. The response includes both the
227
+ closed-out (`current`) and newly-started (`next`) subscriptions. To preview the
228
+ proration credit before committing, call
227
229
  `POST /v3/metering/{bucketId}/subscriptions/{subscriptionId}/change/estimate-credit`
228
230
  with the same body.
229
231
 
@@ -310,9 +312,7 @@ behavior does not apply.
310
312
  curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscriptionId}/cancel \
311
313
  -H "Authorization: Bearer {API_KEY}" \
312
314
  -H "Content-Type: application/json" \
313
- -d '{
314
- "timing": "next_billing_cycle"
315
- }'
315
+ -d '{ "timing": "next_billing_cycle" }'
316
316
  ```
317
317
 
318
318
  `timing` controls when the cancellation takes effect:
@@ -341,9 +341,10 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscri
341
341
  -H "Authorization: Bearer {API_KEY}"
342
342
  ```
343
343
 
344
- This removes the pending cancellation. The subscription continues as normal. For
345
- a subscription whose period has already ended, create a new subscription on the
346
- same plan instead.
344
+ This removes the pending cancellation. The subscription continues as normal.
345
+
346
+ If the subscription has already ended, create a new subscription rather than
347
+ restoring the old one.
347
348
 
348
349
  ## Subscriptions per customer
349
350
 
@@ -114,7 +114,9 @@ jobs:
114
114
  run: npm install
115
115
 
116
116
  - name: Deploy to Zuplo
117
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
117
+ run:
118
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "${{
119
+ github.ref_name }}"
118
120
  env:
119
121
  ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}
120
122
  ```
@@ -171,8 +173,12 @@ jobs:
171
173
  - name: Deploy to Zuplo
172
174
  id: deploy
173
175
  shell: bash
176
+ env:
177
+ # The PR's source branch — not the pull/<number>/merge ref that
178
+ # pull_request events check out
179
+ ENVIRONMENT: ${{ github.head_ref }}
174
180
  run: |
175
- OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
181
+ OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1)
176
182
  echo "$OUTPUT"
177
183
  DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
178
184
  echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
@@ -230,6 +236,18 @@ jobs:
230
236
  --wait
231
237
  ```
232
238
 
239
+ :::caution
240
+
241
+ The deploy step passes `--environment` with the PR's source branch name
242
+ (`github.head_ref`). Workflows triggered by `pull_request` check out the PR
243
+ merge ref (`refs/pull/<number>/merge`), so without `--environment` the CLI names
244
+ the environment after that ref instead of your branch — and any other deploy of
245
+ the same branch creates a second environment with a different URL. See
246
+ [PR Preview Environments](./ci-cd-github/pr-preview-environments.mdx) for
247
+ details.
248
+
249
+ :::
250
+
233
251
  ### Secrets and environment variables
234
252
 
235
253
  Store your Zuplo API key as a GitHub Actions secret:
@@ -10,8 +10,8 @@ tags:
10
10
 
11
11
  Sometimes multiple types of authentication are needed on an API. For example, an
12
12
  API could support JWT Authentication and API Key authentication or two different
13
- OAuth providers (for example Azure AD for employees and Auth0 for partners).
14
- Configuring multiple policies in Zuplo can be done in several ways.
13
+ OAuth providers (for example Microsoft Entra ID for employees and Auth0 for
14
+ partners). Configuring multiple policies in Zuplo can be done in several ways.
15
15
 
16
16
  ## JWT and API Key Authentication
17
17
 
@@ -15,7 +15,8 @@ this is an OpenAPI document used for routing.
15
15
  You can use the Route Designer to build your routes, which will be creating an
16
16
  OpenAPI document in the background for you (check it out in the **JSON View**).
17
17
 
18
- You can also **import an OpenAPI** document by clicking the **Open API** tab.
18
+ You can also **import an OpenAPI** document by clicking the **Import** button in
19
+ the Route Designer.
19
20
 
20
21
  ![Import Open API](../../public/media/open-api/image-1.png)
21
22
 
@@ -39,8 +40,12 @@ document and will keep your Zuplo settings intact, while overwriting everything
39
40
  else from your imported OpenAPI docs. This creates a great workflow, whatever
40
41
  toolset you use.
41
42
 
43
+ <ModalScreenshot>
44
+
42
45
  ![Import OpenAPI dialog](../../public/media/open-api/28512107-8c41-4974-8319-c9ec50734331.png)
43
46
 
47
+ </ModalScreenshot>
48
+
44
49
  What's more, you can now have more confidence that your OpenAPI represents the
45
50
  truth of your API implementation - because it now drives the configuration of
46
51
  your gateway.
@@ -23,8 +23,12 @@ disconnect the project from Source Control.
23
23
  Next create a new project in the correct account if moving accounts or with the
24
24
  correct name. Choose the `Advanced` option on the new project dialog.
25
25
 
26
+ <ModalScreenshot>
27
+
26
28
  ![Import existing project](../../public/media/source-control/image-1.png)
27
29
 
30
+ </ModalScreenshot>
31
+
28
32
  You should see a list of organizations and repositories - pick the source
29
33
  repository you wanted to move and click **Create Project from repository**.
30
34
 
@@ -29,8 +29,12 @@ Open the **Settings** section of your project and select **Environment
29
29
  Variables**. Create a new variable and name it `BACKEND_SECRET`. Set the value
30
30
  to a secure, random value. Ensure that the value is marked as a secret.
31
31
 
32
+ <ModalScreenshot>
33
+
32
34
  ![Set Environment Variable](../../public/media/securing-backend-shared-secret/image.png)
33
35
 
36
+ </ModalScreenshot>
37
+
34
38
  ### Step 2: Create a set header policy
35
39
 
36
40
  Create a policy that sets the `BACKEND_SECRET` as a header on the request to
@@ -40,8 +44,12 @@ is sent to your backend.
40
44
  Navigate to the route you want to secure and add a new policy. Select the **Add
41
45
  or Set Request Headers** policy type and configure it as follows:
42
46
 
47
+ <ModalScreenshot>
48
+
43
49
  ![Set Header Policy](../../public/media/securing-backend-shared-secret/image-1.png)
44
50
 
51
+ </ModalScreenshot>
52
+
45
53
  The configuration uses the environment variable via the `$env(BACKEND_SECRET)`
46
54
  selector as shown below.
47
55
 
@@ -98,10 +106,10 @@ interested in using this option please contact us at `support@zuplo.com`.
98
106
  Utilize the IAM controls provided by your Cloud host to secure inbound requests
99
107
  and allow only authorized service principals access to your service.
100
108
 
101
- - For Azure users, you can user our
109
+ - For Azure users, you can use the
102
110
  [Upstream Azure AD Service Auth](../policies/upstream-azure-ad-service-auth-inbound.mdx)
103
- policy. This uses Azure AD App registrations to create a token that Zuplo will
104
- send to requests to Azure.
111
+ policy. This uses Microsoft Entra ID (formerly Azure AD) App registrations to
112
+ create a token that Zuplo sends with requests to Azure.
105
113
 
106
114
  - For GCP users, you can use our
107
115
  [Upstream GCP Service AUth](../policies/upstream-gcp-service-auth-inbound.mdx)
@@ -130,8 +130,12 @@ If you have an existing GitHub repository that contains a Zuplo project, you can
130
130
  connect to that repository when you create a new project. Select **Import
131
131
  existing project** then select your GitHub organization and repository.
132
132
 
133
+ <ModalScreenshot>
134
+
133
135
  ![Import existing project to Zuplo](../../public/media/source-control/image-1.png)
134
136
 
137
+ </ModalScreenshot>
138
+
135
139
  ## What's Included
136
140
 
137
141
  With GitHub connected, you get:
@@ -226,8 +226,8 @@ period of inactivity is slow. Subsequent requests are fast.
226
226
 
227
227
  ### Analytics Dashboard
228
228
 
229
- Zuplo's analytics dashboard provides at-a-glance visibility into your API's
230
- performance. Use it to:
229
+ Zuplo's analytics dashboard, in the **Observability** tab of your project,
230
+ provides at-a-glance visibility into your API's performance. Use it to:
231
231
 
232
232
  - Identify slow endpoints by reviewing request latency data
233
233
  - Filter by route, API key, or time period to isolate patterns
@@ -177,9 +177,9 @@ const response = await fetch("https://api.example.com/data", {
177
177
  ### Portal live logs
178
178
 
179
179
  The Zuplo Portal provides real-time log viewing for deployed environments. Open
180
- the [**Environments**](https://portal.zuplo.com/+/account/project/environments)
181
- tab in your project, select the deployed environment, and open the logs tab to
182
- see live request logs and any messages logged with `context.log`.
180
+ the **Observability** tab in your project — the **Logs** view opens by default —
181
+ then use the **Environment** filter to select the deployed environment and see
182
+ live request logs and any messages logged with `context.log`.
183
183
 
184
184
  ### Using context.log
185
185
 
@@ -122,6 +122,38 @@ zuplo deploy --project my-project
122
122
  zuplo deploy --project my-project --environment my-env-name
123
123
  ```
124
124
 
125
+ ### Deploying from CI/CD
126
+
127
+ Without `--environment`, the CLI names the environment after the current git
128
+ branch. CI systems usually check out a detached HEAD, in which case the CLI
129
+ resolves the branch from the remote branches that contain the checked-out
130
+ commit. Two cases break this inference:
131
+
132
+ - On GitHub Actions `pull_request` events, the checkout is the pull request
133
+ merge ref (`refs/pull/<number>/merge`) — a commit that doesn't exist on any
134
+ branch — so the environment is named after that ref instead of your branch.
135
+ - When the checked-out commit exists on more than one branch, the first match
136
+ wins, which may not be the branch that triggered the build.
137
+
138
+ Always pass `--environment` explicitly in CI so that every trigger deploys the
139
+ same, predictably named environment:
140
+
141
+ ```bash
142
+ # GitHub Actions: github.head_ref is the source branch on pull_request
143
+ # events; github.ref_name is the branch on push events
144
+ zuplo deploy --project my-project --environment "$BRANCH_NAME"
145
+ ```
146
+
147
+ Deploying the same environment name always updates the same environment and
148
+ keeps its URL stable across deploys. This matters whenever an external system
149
+ must match the URL exactly — an OIDC token audience, a webhook registration, or
150
+ an allowlist. Capture the URL from the deploy output (`Deployed to
151
+ https://...`) rather than constructing it from the branch name: the URL
152
+ hostname uses a normalized, truncated form of the environment name plus a
153
+ unique identifier. See
154
+ [Branch-Based Deployments](../articles/branch-based-deployments.mdx) for the
155
+ naming rules.
156
+
125
157
  ## Polling timeout
126
158
 
127
159
  By default, the deploy command polls the status of the deployment every second