zuplo 7.4.1 → 7.4.3

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 (58) hide show
  1. package/docs/api-management/introduction.md +1 -1
  2. package/docs/articles/api-key-api.mdx +3 -3
  3. package/docs/articles/custom-ci-cd-azure.mdx +1 -1
  4. package/docs/articles/custom-ci-cd-bitbucket.mdx +2 -2
  5. package/docs/articles/custom-code-patterns.md +1 -1
  6. package/docs/articles/custom-domains.mdx +2 -2
  7. package/docs/articles/development-options.mdx +4 -4
  8. package/docs/articles/fastly-zuplo-host-setup.mdx +5 -5
  9. package/docs/articles/feature-flags.mdx +442 -0
  10. package/docs/articles/gke-with-upstream-auth-policy.mdx +7 -7
  11. package/docs/articles/health-checks.mdx +4 -4
  12. package/docs/articles/hosting-options.mdx +2 -2
  13. package/docs/articles/local-development-env-variables.mdx +1 -1
  14. package/docs/articles/metrics-plugins.mdx +1 -1
  15. package/docs/articles/migration-overview.md +1 -1
  16. package/docs/articles/monorepo-deployment.mdx +10 -10
  17. package/docs/articles/performance-testing.mdx +10 -9
  18. package/docs/articles/plugin-azure-blob.mdx +1 -1
  19. package/docs/articles/plugin-azure-event-hubs.mdx +2 -2
  20. package/docs/articles/policies.mdx +2 -2
  21. package/docs/articles/securing-backend-mtls.mdx +3 -3
  22. package/docs/articles/securing-the-gateway-with-client-mtls.mdx +3 -3
  23. package/docs/articles/securing-your-backend.mdx +1 -1
  24. package/docs/articles/security.mdx +1 -1
  25. package/docs/articles/terraform.mdx +2 -2
  26. package/docs/articles/testing.mdx +6 -6
  27. package/docs/articles/troubleshooting-slow-responses.mdx +5 -6
  28. package/docs/articles/troubleshooting.md +1 -1
  29. package/docs/articles/use-openapi-extension-data.mdx +4 -4
  30. package/docs/articles/waf-ddos-akamai.md +13 -13
  31. package/docs/articles/waf-ddos-aws-waf-shield.mdx +14 -14
  32. package/docs/articles/waf-ddos-fastly.mdx +14 -14
  33. package/docs/articles/waf-ddos.mdx +7 -7
  34. package/docs/cli/authentication.mdx +1 -1
  35. package/docs/cli/lint.mdx +103 -0
  36. package/docs/concepts/how-zuplo-works.mdx +5 -4
  37. package/docs/concepts/upstream-credentials.mdx +1 -1
  38. package/docs/dedicated/akamai/architecture.mdx +23 -24
  39. package/docs/dedicated/akamai/cdn.mdx +20 -20
  40. package/docs/dedicated/architecture.mdx +28 -27
  41. package/docs/dedicated/custom-domains.mdx +3 -4
  42. package/docs/dedicated/federated-gateways.mdx +1 -1
  43. package/docs/dedicated/networking.mdx +10 -10
  44. package/docs/dedicated/overview.mdx +3 -3
  45. package/docs/dedicated/source-control.mdx +6 -5
  46. package/docs/handlers/custom-handler.mdx +4 -4
  47. package/docs/handlers/mcp-server.mdx +1 -1
  48. package/docs/handlers/url-forward.mdx +1 -2
  49. package/docs/mcp-server/introduction.mdx +4 -4
  50. package/docs/policies/ai-gateway-dlp-inbound/doc.md +79 -45
  51. package/docs/policies/ai-gateway-dlp-inbound/schema.json +5679 -3359
  52. package/docs/programmable-api/environment.mdx +1 -1
  53. package/docs/programmable-api/jwt-service-plugin.mdx +1 -1
  54. package/docs/programmable-api/logger.mdx +1 -1
  55. package/docs/programmable-api/runtime-errors.mdx +1 -1
  56. package/docs/programmable-api/zuplo-context.mdx +3 -3
  57. package/docs/self-hosted/overview.md +3 -3
  58. package/package.json +5 -5
@@ -3,7 +3,7 @@ title: Zuplo API Management
3
3
  sidebar_label: Introduction
4
4
  ---
5
5
 
6
- The Zuplo API Gateway is a fully-managed, lightweight API management platform
6
+ The Zuplo Gateway is a fully-managed, lightweight API management platform
7
7
  designed for developers. It offers fast deployment, GitOps-friendly workflows,
8
8
  and unlimited environments. Whether you're an individual developer or part of an
9
9
  engineering team, Zuplo makes it easy to:
@@ -45,8 +45,8 @@ Consumers can contain one or more API Keys.
45
45
 
46
46
  Buckets are the top level group for this service. A bucket could be used with a
47
47
  single Zuplo environment or shared among multiple environments or projects. By
48
- default a Zuplo API Gateway project will be created with several buckets that
49
- map to production, preview, and development (working copy) environments.
48
+ default a Zuplo Gateway project will be created with several buckets that map to
49
+ production, preview, and development (working copy) environments.
50
50
 
51
51
  Enterprise plan customers run complex configurations where buckets are shared
52
52
  across gateway projects or even accounts. This can allow your end-users to
@@ -148,7 +148,7 @@ The response will look like this:
148
148
  }
149
149
  ```
150
150
 
151
- You can use this API Key to call your Zuplo API Gateway that's protected by the
151
+ You can use this API Key to call your Zuplo Gateway that's protected by the
152
152
  [API Key Authentication](/docs/policies/api-key-inbound) policy.
153
153
 
154
154
  ### Query Consumers with API Keys By Tags
@@ -3,7 +3,7 @@ title: "Azure Pipelines"
3
3
  sidebar_label: Azure Pipelines
4
4
  ---
5
5
 
6
- Azure Pipelines brings enterprise-grade CI/CD to your Zuplo API gateway. Build
6
+ Azure Pipelines brings enterprise-grade CI/CD to your Zuplo Gateway. Build
7
7
  complex deployment workflows with stages, approvals, and integrations across
8
8
  your Azure ecosystem.
9
9
 
@@ -4,8 +4,8 @@ sidebar_label: Bitbucket Pipelines
4
4
  ---
5
5
 
6
6
  Bitbucket Pipelines brings CI/CD directly into your Bitbucket workflow. Define
7
- pipelines in `bitbucket-pipelines.yml` and deploy your Zuplo API gateway
8
- alongside your code reviews and pull requests.
7
+ pipelines in `bitbucket-pipelines.yml` and deploy your Zuplo Gateway alongside
8
+ your code reviews and pull requests.
9
9
 
10
10
  ## Why Bitbucket Pipelines with Zuplo?
11
11
 
@@ -5,7 +5,7 @@ sidebar_label: Custom Code
5
5
 
6
6
  Zuplo is fully programmable. You can write custom TypeScript code for inbound
7
7
  policies, outbound policies, and request handlers to extend every part of your
8
- API gateway. This guide covers the function signatures, common patterns, and
8
+ Zuplo Gateway. This guide covers the function signatures, common patterns, and
9
9
  best practices for writing custom code.
10
10
 
11
11
  ## Custom Inbound Policy
@@ -208,8 +208,8 @@ enabled. If you wish to upgrade to a higher TLS version, please contact
208
208
 
209
209
  <EnterpriseFeature name="Alias Domains" />
210
210
 
211
- Sometimes your API Gateway might be running behind another service such as a
212
- CDN, WAF or load balancer. In this case, your API Gateway's domain may not be
211
+ Sometimes your Zuplo Gateway might be running behind another service such as a
212
+ CDN, WAF or load balancer. In this case, your Zuplo Gateway's domain may not be
213
213
  the same as the domain your clients use to access your API. In this case, you
214
214
  can add an alias domain to your Zuplo project. An alias domain will configure
215
215
  your API and documentation to use the alias domain for any public facing URLs
@@ -8,9 +8,9 @@ flexible and fastest way to work with your Zuplo API and Developer Portal.
8
8
 
9
9
  ## Local Development
10
10
 
11
- The [local experience](./local-development.mdx) allows users to run both the API
12
- Gateway and Dev Portal locally. It also includes a local version of the Route
13
- Designer that's integrated into the Zuplo Portal (portal.zuplo.com). Local
11
+ The [local experience](./local-development.mdx) allows users to run both the
12
+ Zuplo Gateway and Dev Portal locally. It also includes a local version of the
13
+ Route Designer that's integrated into the Zuplo Portal (portal.zuplo.com). Local
14
14
  development allows customers to use the IDE of their choice and run everything
15
15
  locally.
16
16
 
@@ -20,7 +20,7 @@ iterate on their APIs without needing to deploy to the cloud.
20
20
 
21
21
  Local development is the fastest way to develop and test your APIs as you can
22
22
  test changes immediately without waiting for a deployment. Changes to your API
23
- are automatically loaded into the local API Gateway and Dev Portal.
23
+ are automatically loaded into the local Zuplo Gateway and Dev Portal.
24
24
 
25
25
  ## Web-Based Development Experience
26
26
 
@@ -8,7 +8,7 @@ necessary setup for most people as Zuplo already runs at the edge and can be
8
8
  used for CDN like caching. However, there are some scenarios where you may want
9
9
  to put Zuplo behind Fastly. For example, if you are using Fastly's
10
10
  [WAF or DDoS protection](./waf-ddos-fastly.mdx) and want to ensure that all
11
- traffic goes through Fastly before hitting your Zuplo API Gateway.
11
+ traffic goes through Fastly before hitting your Zuplo Gateway.
12
12
 
13
13
  ## Configuring Zuplo as a Fastly Host
14
14
 
@@ -16,15 +16,15 @@ The following settings will allow you to run Zuplo as a host behind Fastly.
16
16
 
17
17
  ### Zuplo Configuration
18
18
 
19
- It's recommended that you use a custom domain for your Zuplo API Gateway. This
20
- will allow you to more configure your Fastly host as well as ensure that your
21
- domain is stable regardless of your Zuplo configuration.
19
+ It's recommended that you use a custom domain for your Zuplo Gateway. This will
20
+ allow you to more configure your Fastly host as well as ensure that your domain
21
+ is stable regardless of your Zuplo configuration.
22
22
 
23
23
  ### Fastly Configuration
24
24
 
25
25
  1. Create a new Fastly CDN service or use an existing one.
26
26
  2. Create a new Origin and name it whatever you like.
27
- 3. Set the **Address** to your Zuplo API Gateway domain. For example,
27
+ 3. Set the **Address** to your Zuplo Gateway domain. For example,
28
28
  `api.example.com` if using a custom domain, or
29
29
  `my-project-main-021839d.zuplo.app` without a custom domain.
30
30
  4. Enable TLS and keep the port as `443`.
@@ -0,0 +1,442 @@
1
+ ---
2
+ title: Use Feature Flags to Gate Your API
3
+ sidebar_label: Feature Flags & Gating
4
+ description:
5
+ Evaluate feature flags from providers like Statsig, PostHog, Flagsmith, and
6
+ GrowthBook inside Zuplo policies. Cache per-user evaluations for low latency
7
+ and load global flags in the background so they never block a request.
8
+ tags:
9
+ - integrations
10
+ - custom-code
11
+ - caching
12
+ ---
13
+
14
+ Zuplo doesn't have a dedicated feature-flag policy — instead, every provider
15
+ works, because you evaluate flags in custom code. This guide shows the patterns
16
+ that keep flag checks fast on a gateway, with worked examples for Statsig,
17
+ PostHog, Flagsmith, and GrowthBook:
18
+
19
+ - **Live evaluation with caching** — call the provider's API on the request and
20
+ cache the result per user for a short TTL. Use for flags targeted at
21
+ individual users, plans, or tenants.
22
+ - **Background loading** — load flags that apply globally (kill switches,
23
+ maintenance mode, flag definitions) with the
24
+ [BackgroundLoader](../programmable-api/background-loader.mdx), which refreshes
25
+ out of band instead of blocking requests.
26
+ - **Provider SDK with local evaluation** — evaluate flags in-process with no
27
+ per-request provider call, at the cost of a heavier cold start. See
28
+ [Statsig, fully local](#statsig-fully-local).
29
+
30
+ ## Choose a pattern
31
+
32
+ | Pattern | Best for | Latency cost per request |
33
+ | ---------------------------------- | -------------------------------------------------- | --------------------------------------------------- |
34
+ | Live evaluation + cache | Per-user or per-plan targeting | One provider call per user per TTL window |
35
+ | Background loading | Global flags, kill switches, flag definition files | None in the steady state; refreshes run out of band |
36
+ | Provider SDK with local evaluation | Complex targeting rules, many gates per request | None after initialization, but heavier cold start |
37
+
38
+ Most APIs mix these: a background-loaded global flag set plus a small number of
39
+ cached per-user checks on the routes that need them.
40
+
41
+ ## Prerequisites
42
+
43
+ - A Zuplo project with an authentication policy on the routes you want to gate
44
+ (for example [API key authentication](../policies/api-key-inbound.mdx)). The
45
+ examples read `request.user`, which an inbound authentication policy populates
46
+ — `request.user.sub` is the user identifier and `request.user.data` carries
47
+ JWT claims or API key metadata such as the customer's `plan`. See
48
+ [Request User](../programmable-api/request-user.mdx).
49
+ - An API key or token from your flag provider, stored as a secret
50
+ [environment variable](./environment-variables.mdx). Where to find each one:
51
+
52
+ | Variable | Provider | Where to find it | Secret? |
53
+ | --------------------------- | ---------- | -------------------------------------------------- | ------- |
54
+ | `STATSIG_SERVER_KEY` | Statsig | Console → Settings → API Keys → Server Secret Key | Yes |
55
+ | `STATSIG_CLIENT_KEY` | Statsig | Console → Settings → API Keys → Client SDK Key | No |
56
+ | `POSTHOG_PROJECT_TOKEN` | PostHog | Project Settings → Project ID → project token | No |
57
+ | `FLAGSMITH_ENVIRONMENT_KEY` | Flagsmith | Environment settings → client-side environment key | No |
58
+ | `GROWTHBOOK_CLIENT_KEY` | GrowthBook | SDK Connections → client key | No |
59
+
60
+ New environment variables require a redeployment before your code sees them.
61
+
62
+ You only need the variables for the providers you use — skip the rest.
63
+
64
+ ## Pattern 1: Live evaluation with caching
65
+
66
+ Every major provider exposes an HTTP endpoint that evaluates a flag for a user
67
+ and returns the result. Call it on the first request, then cache the result in a
68
+ [MemoryZoneReadThroughCache](../programmable-api/memory-zone-read-through-cache.mdx)
69
+ so subsequent requests for the same user skip the network call.
70
+
71
+ This example checks a Statsig gate and caches the result for 60 seconds:
72
+
73
+ ```ts title="modules/feature-gate-policy.ts"
74
+ import {
75
+ HttpProblems,
76
+ MemoryZoneReadThroughCache,
77
+ ZuploContext,
78
+ ZuploRequest,
79
+ environment,
80
+ } from "@zuplo/runtime";
81
+
82
+ const CACHE_NAME = "feature-flags";
83
+ const CACHE_TTL_SECONDS = 60;
84
+
85
+ async function isGateEnabled(
86
+ gateName: string,
87
+ userId: string,
88
+ context: ZuploContext,
89
+ ): Promise<boolean> {
90
+ const cache = new MemoryZoneReadThroughCache<boolean>(CACHE_NAME, context);
91
+ const cacheKey = `statsig:${gateName}:${userId}`;
92
+
93
+ const cached = await cache.get(cacheKey);
94
+ if (cached !== undefined) {
95
+ return cached;
96
+ }
97
+
98
+ const response = await fetch("https://api.statsig.com/v1/check_gate", {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ "statsig-api-key": environment.STATSIG_SERVER_KEY,
103
+ },
104
+ body: JSON.stringify({
105
+ gateName,
106
+ user: { userID: userId },
107
+ }),
108
+ });
109
+
110
+ if (!response.ok) {
111
+ // Fail closed: if Statsig is unreachable, deny the feature rather
112
+ // than exposing something that isn't rolled out. Choose deliberately —
113
+ // see "Decide how to fail" below.
114
+ context.log.error(`Statsig check failed: ${response.status}`);
115
+ return false;
116
+ }
117
+
118
+ const result = await response.json();
119
+ const enabled = result.value === true;
120
+ cache.put(cacheKey, enabled, CACHE_TTL_SECONDS);
121
+ return enabled;
122
+ }
123
+
124
+ export default async function policy(
125
+ request: ZuploRequest,
126
+ context: ZuploContext,
127
+ ) {
128
+ const userId = request.user?.sub;
129
+ if (!userId) {
130
+ return HttpProblems.unauthorized(request, context);
131
+ }
132
+
133
+ const enabled = await isGateEnabled("new-beta-endpoint", userId, context);
134
+ if (!enabled) {
135
+ return HttpProblems.forbidden(request, context, {
136
+ detail: "This feature is not enabled for your account",
137
+ });
138
+ }
139
+
140
+ return request;
141
+ }
142
+ ```
143
+
144
+ Only the first request for each user pays for the Statsig call; every request
145
+ after that reads from memory until the TTL expires. Keep the TTL short (30–120
146
+ seconds) so flag changes in the provider propagate quickly. To check several
147
+ gates, fire the `check_gate` calls in parallel with `Promise.all` and cache each
148
+ result under its own key.
149
+
150
+ ### Wire up the policy
151
+
152
+ Register the module as a custom code policy in `config/policies.json`:
153
+
154
+ ```json title="config/policies.json"
155
+ {
156
+ "name": "feature-gate",
157
+ "policyType": "custom-code-inbound",
158
+ "handler": {
159
+ "export": "default",
160
+ "module": "$import(./modules/feature-gate-policy)"
161
+ }
162
+ }
163
+ ```
164
+
165
+ Then add it to the inbound policy chain of each route you want to gate, after
166
+ authentication:
167
+
168
+ ```json title="config/routes.oas.json"
169
+ {
170
+ "paths": {
171
+ "/beta/insights": {
172
+ "get": {
173
+ "x-zuplo-route": {
174
+ "policies": {
175
+ "inbound": ["api-key-auth", "feature-gate"]
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Verify the gate
185
+
186
+ Call the route twice — once with the flag off and once on:
187
+
188
+ - **Flag off** — the request returns `403` with an `application/problem+json`
189
+ body: `"detail": "This feature is not enabled for your account"`.
190
+ - **Flag on** — the request passes through to the handler normally.
191
+
192
+ Because the example fails closed, a bad key or unreachable Statsig returns the
193
+ same 403 as a disabled flag. Watch the logs for the `Statsig check failed:`
194
+ error line to tell a provider outage apart from a legitimately denied request.
195
+
196
+ ## Pattern 2: Background loading for global flags
197
+
198
+ Some flags apply to everyone: kill switches, maintenance mode, or a global
199
+ rollout. Others are cheapest to fetch as one shared definition file rather than
200
+ per-user evaluations. Either way, there's no reason to make a request wait on
201
+ that fetch — load it in the background instead.
202
+
203
+ The [BackgroundLoader](../programmable-api/background-loader.mdx) returns cached
204
+ data immediately when available, refreshes it asynchronously when the TTL
205
+ expires, and only blocks while the cache is empty on a cold start. Create it at
206
+ module level so the loader is shared across requests handled by the same worker
207
+ process ([isolate](../concepts/how-zuplo-works.mdx)).
208
+
209
+ GrowthBook is a natural fit here: its CDN returns the full feature definition
210
+ payload as a single cacheable JSON document.
211
+
212
+ ```ts title="modules/global-flags-policy.ts"
213
+ import {
214
+ BackgroundLoader,
215
+ HttpProblems,
216
+ ZuploContext,
217
+ ZuploRequest,
218
+ environment,
219
+ } from "@zuplo/runtime";
220
+
221
+ interface GrowthBookFeatures {
222
+ [flagName: string]: { defaultValue: unknown };
223
+ }
224
+
225
+ const featureLoader = new BackgroundLoader<GrowthBookFeatures>(
226
+ async () => {
227
+ const response = await fetch(
228
+ `https://cdn.growthbook.io/api/features/${environment.GROWTHBOOK_CLIENT_KEY}`,
229
+ );
230
+ if (!response.ok) {
231
+ throw new Error(`Failed to load features: ${response.status}`);
232
+ }
233
+ const data = await response.json();
234
+ return data.features;
235
+ },
236
+ {
237
+ ttlSeconds: 60,
238
+ loaderTimeoutSeconds: 5,
239
+ },
240
+ );
241
+
242
+ export default async function policy(
243
+ request: ZuploRequest,
244
+ context: ZuploContext,
245
+ ) {
246
+ // Returns cached definitions immediately in the steady state;
247
+ // the only blocking case is an empty cache on a cold start.
248
+ const features = await featureLoader.get("features");
249
+
250
+ if (features["api-maintenance-mode"]?.defaultValue === true) {
251
+ return HttpProblems.serviceUnavailable(request, context, {
252
+ detail: "This API is temporarily unavailable for maintenance",
253
+ });
254
+ }
255
+
256
+ return request;
257
+ }
258
+ ```
259
+
260
+ Register this module as a `custom-code-inbound` policy in `config/policies.json`
261
+ and add it to the routes you want to gate, exactly as in pattern 1.
262
+
263
+ The same pattern works for any provider payload that isn't user-specific — for
264
+ example a Statsig gate evaluated against a fixed global user, or the Flagsmith
265
+ environment document. One BackgroundLoader per provider keeps each payload on
266
+ its own refresh cadence.
267
+
268
+ :::note
269
+
270
+ The `defaultValue` shortcut covers simple on/off flags. Flags with targeting
271
+ rules or percentage rollouts need an evaluator — either evaluate remotely
272
+ (pattern 1) or pass the definitions to the provider's JavaScript SDK.
273
+
274
+ :::
275
+
276
+ ## More providers
277
+
278
+ The live-evaluation pattern is the same everywhere; only the endpoint, headers,
279
+ and response shape change. Swap the fetch call inside the cached helper — and,
280
+ for providers like PostHog that evaluate all flags in one call, cache the whole
281
+ map keyed by user instead of a boolean keyed by gate and user.
282
+
283
+ ### PostHog
284
+
285
+ PostHog evaluates **all** flags for a user in one call, so cache the whole map
286
+ per `distinct_id`. Use your project token — it's safe for this public endpoint,
287
+ though you should still store it in an environment variable.
288
+
289
+ ```ts
290
+ const response = await fetch("https://us.i.posthog.com/flags?v=2", {
291
+ method: "POST",
292
+ headers: { "Content-Type": "application/json" },
293
+ body: JSON.stringify({
294
+ api_key: environment.POSTHOG_PROJECT_TOKEN,
295
+ distinct_id: userId,
296
+ person_properties: { plan: request.user?.data?.plan },
297
+ }),
298
+ });
299
+
300
+ const data = await response.json();
301
+ // Cache the whole map: cache.put(`posthog:${userId}`, data.flags, 60)
302
+ // data.flags["new-checkout"] = { key, enabled, variant, metadata: { payload } }
303
+ const enabled = data.flags?.["new-checkout"]?.enabled === true;
304
+ const variant = data.flags?.["new-checkout"]?.variant; // multivariate flags
305
+ ```
306
+
307
+ Use `https://eu.i.posthog.com` if your PostHog project is hosted in the EU.
308
+
309
+ ### Flagsmith
310
+
311
+ Flagsmith's edge API evaluates flags for an identity with a simple GET. Use the
312
+ client-side environment key (not a server-side SDK key) — it's designed to be
313
+ public.
314
+
315
+ ```ts
316
+ const response = await fetch(
317
+ `https://edge.api.flagsmith.com/api/v1/identities/?identifier=${encodeURIComponent(userId)}`,
318
+ {
319
+ headers: { "X-Environment-Key": environment.FLAGSMITH_ENVIRONMENT_KEY },
320
+ },
321
+ );
322
+
323
+ const data = await response.json();
324
+ const flag = data.flags?.find((f) => f.feature.name === "new-checkout");
325
+ const enabled = flag?.enabled === true;
326
+ const value = flag?.feature_state_value; // remote config value
327
+ ```
328
+
329
+ ### Statsig, fully local
330
+
331
+ If you need many gates evaluated per request with zero provider calls, use
332
+ Statsig's `@statsig/serverless-client` SDK, which is built for short-lived
333
+ worker processes. Unlike the HTTP API examples above (which use the server
334
+ secret key), the serverless client evaluates on-device and authenticates with
335
+ the publishable client SDK key.
336
+
337
+ Zuplo runs a custom JavaScript engine rather than Node.js, so third-party
338
+ packages must be bundled into your project before you can import them — see
339
+ [Node Modules](../programmable-api/node-modules.mdx). Bundle the SDK once, then
340
+ import it from the bundled path:
341
+
342
+ ```bash
343
+ npm install @statsig/serverless-client
344
+ npx tsdown ./node_modules/@statsig/serverless-client --format esm --platform browser --out-dir ./modules/third-party/statsig
345
+ ```
346
+
347
+ Initialize the client once at module level so the configuration download happens
348
+ on cold start, not per request. Evaluate gates locally, and flush exposure
349
+ events with
350
+ [`context.waitUntil`](../programmable-api/zuplo-context.mdx#waituntil) so
351
+ analytics never add latency to a response:
352
+
353
+ ```ts title="modules/statsig-local-policy.ts"
354
+ import {
355
+ HttpProblems,
356
+ ZuploContext,
357
+ ZuploRequest,
358
+ environment,
359
+ } from "@zuplo/runtime";
360
+ import { StatsigServerlessClient } from "./third-party/statsig/index.mjs";
361
+
362
+ const client = new StatsigServerlessClient(environment.STATSIG_CLIENT_KEY);
363
+ // Kicks off the config download once per worker process, not per request
364
+ const ready = client.initializeAsync();
365
+
366
+ export default async function policy(
367
+ request: ZuploRequest,
368
+ context: ZuploContext,
369
+ ) {
370
+ await ready;
371
+ const enabled = client.checkGate("new-beta-endpoint", {
372
+ userID: request.user?.sub,
373
+ });
374
+ context.waitUntil(client.flush());
375
+
376
+ if (!enabled) {
377
+ return HttpProblems.forbidden(request, context, {
378
+ detail: "This feature is not enabled for your account",
379
+ });
380
+ }
381
+ return request;
382
+ }
383
+ ```
384
+
385
+ ### What about LaunchDarkly?
386
+
387
+ LaunchDarkly is the least HTTP-friendly option. Its server-side SDK holds a
388
+ persistent streaming connection open and relies on Node.js APIs that don't run
389
+ in Zuplo's engine, and LaunchDarkly doesn't document a plain-HTTP endpoint for
390
+ server-side evaluation. Its
391
+ [edge SDKs](https://launchdarkly.com/docs/sdk/server-side) evaluate locally but
392
+ assume platform-specific storage bindings (Cloudflare KV, Vercel Edge Config,
393
+ and similar), so they don't drop directly into a Zuplo project either. If
394
+ LaunchDarkly is your provider, the practical paths are evaluating flags in your
395
+ backend behind the gateway, or syncing flag state into a store your Zuplo code
396
+ reads with the patterns above.
397
+
398
+ ## Gate an MCP server with flags
399
+
400
+ If you're fronting an MCP server with the
401
+ [MCP Gateway](../mcp-gateway/introduction.mdx), the same policies apply — MCP
402
+ routes run the standard inbound policy chain before the handler. Add your
403
+ feature-gate policy to the MCP route to control access per user or plan, for
404
+ example to make MCP access a paid-tier feature or to turn off a beta server.
405
+
406
+ For curating _which tools_ a server exposes, start with the static
407
+ [capability filtering](../mcp-gateway/capability-filtering.mdx) policy, which
408
+ needs no code. Feature flags complement it when the availability of the server
409
+ itself must change dynamically per user without a redeploy.
410
+
411
+ ## Best practices
412
+
413
+ 1. **Decide how to fail.** If the provider is unreachable, fail closed (deny the
414
+ feature) for rollouts of risky functionality, or fail open (allow) for
415
+ cosmetic features. The examples above fail closed and log the error.
416
+ 2. **Keep per-user TTLs short.** 30–120 seconds balances provider traffic
417
+ against flag-change propagation. Global definitions can tolerate longer.
418
+ 3. **Cache the smallest useful value.** A boolean per gate per user stays well
419
+ within memory limits; don't cache whole provider responses you don't need.
420
+ Remember a Zuplo process has roughly 120 MB of memory — see
421
+ [Lazy Load Configuration](./lazy-load-configuration-into-cache.mdx) for the
422
+ caching trade-offs.
423
+ 4. **Store keys in environment variables.** Use secret
424
+ [environment variables](./environment-variables.mdx) for server keys like
425
+ Statsig's server secret; never hardcode them.
426
+ 5. **Log exposures in the background.** Providers use exposure events for
427
+ analytics and experiments. Send them with
428
+ [`context.waitUntil`](../programmable-api/zuplo-context.mdx#waituntil) (as in
429
+ the Statsig SDK example) so analytics never add latency to a request.
430
+
431
+ ## Next steps
432
+
433
+ - [BackgroundLoader](../programmable-api/background-loader.mdx) — the primitive
434
+ behind pattern 2
435
+ - [MemoryZoneReadThroughCache](../programmable-api/memory-zone-read-through-cache.mdx)
436
+ and [ZoneCache](../programmable-api/zone-cache.mdx) — the caching layers
437
+ - [Custom code policies](../policies/custom-code-inbound.mdx) — how inbound
438
+ policies are wired and run
439
+ - [Lazy Load Configuration](./lazy-load-configuration-into-cache.mdx) — the
440
+ general config-caching pattern this guide builds on
441
+ - [Curate the tools an upstream exposes](../mcp-gateway/how-to/curate-tools.mdx)
442
+ — static MCP tool curation to combine with flags
@@ -3,12 +3,12 @@ title: Secure a GCP Backend with Zuplo Upstream Auth
3
3
  sidebar_label: GCP Backend
4
4
  ---
5
5
 
6
- When using any API gateway as your API's entry point, it's critical that only
7
- traffic that originates from the Gateway is allowed to call your backend. There
6
+ When using any gateway as your API's entry point, it's critical that only
7
+ traffic that originates from the gateway is allowed to call your backend. There
8
8
  are [many way to this](./securing-your-backend.mdx) depending on your
9
9
  requirements and how your backend is hosted. This article will explain how to
10
10
  use Google IAM to secure a GKE cluster so that only requests made through your
11
- Zuplo API Gateway will be allowed to call your GKE ingress.
11
+ Zuplo Gateway will be allowed to call your GKE ingress.
12
12
 
13
13
  Using GCP IAM to authorize your Zuplo Gateway to make requests to your backend
14
14
  utilizes Googles core IAM system known as
@@ -33,11 +33,11 @@ set up behind an HTTP Load Balancer can be used.
33
33
  The diagram below shows how the end-to-end system interacts. The important steps
34
34
  to this process are:
35
35
 
36
- 1. The client makes a request to your Zuplo API Gateway
37
- 1. The Zuplo API Gateway enforces any policies (for example authentication,
36
+ 1. The client makes a request to your Zuplo Gateway
37
+ 1. The Zuplo Gateway enforces any policies (for example authentication,
38
38
  authorization, rate limiting)
39
- 1. The Zuplo API Gateway proxies the request to the public IP address of your
40
- GCP Load Balancer. Zuplo adds authorization information to the request that
39
+ 1. The Zuplo Gateway proxies the request to the public IP address of your GCP
40
+ Load Balancer. Zuplo adds authorization information to the request that
41
41
  identify the request as coming from Zuplo
42
42
  1. GCP Identity Aware proxy validates that the request comes from an authorized
43
43
  Service Account identity. Unauthorized requests are rejected.
@@ -2,19 +2,19 @@
2
2
  title: Health Check Handler
3
3
  sidebar_label: Health Checks
4
4
  description:
5
- Learn how to set up health check endpoints to monitor your API Gateway and
5
+ Learn how to set up health check endpoints to monitor your Zuplo Gateway and
6
6
  backend services.
7
7
  tags:
8
8
  - observability
9
9
  - backends
10
10
  ---
11
11
 
12
- Part of running a reliable API Gateway is ensuring that the service is healthy
12
+ Part of running a reliable Zuplo Gateway is ensuring that the service is healthy
13
13
  and available. With Zuplo, it's easy to set up a health check endpoint that can
14
14
  be used to ensure the health of both your Zuplo Gateway and your backend (as
15
15
  well as the connectivity between them).
16
16
 
17
- A typical health check endpoint on your API Gateway will exercise some of the
17
+ A typical health check endpoint on your Zuplo Gateway will exercise some of the
18
18
  most important policies (such as authentication) and then make a simple request
19
19
  to your backend to ensure that it's reachable and functioning.
20
20
 
@@ -32,7 +32,7 @@ OpenAPI file this would look like:
32
32
  "/health": {
33
33
  "get": {
34
34
  "summary": "Health Check",
35
- "description": "Checks the health of the API Gateway and backend.",
35
+ "description": "Checks the health of the Zuplo Gateway and backend.",
36
36
  "responses": {
37
37
  "200": {
38
38
  "description": "OK"
@@ -32,7 +32,7 @@ your service hosted to meet any sovereignty or data residency concerns.
32
32
 
33
33
  Managed Dedicated is ideal for organizations that need:
34
34
 
35
- - To run your API Gateway on a specific cloud provider
35
+ - To run your Zuplo Gateway on a specific cloud provider
36
36
  - Custom networking configurations, such as restricting access to the public
37
37
  internet
38
38
  - Geographical deployment requirements where Managed Edge isn't feasible
@@ -50,7 +50,7 @@ Run Zuplo on your own infrastructure in any cloud or private data center. Zuplo
50
50
  Self-Hosted runs exclusively on Kubernetes and is installed with a single Helm
51
51
  chart into your cluster.
52
52
 
53
- In the standard hybrid deployment model, you run the Zuplo API Gateway and its
53
+ In the standard hybrid deployment model, you run the Zuplo Gateway and its
54
54
  management plane on your Kubernetes cluster, while a small set of Zuplo cloud
55
55
  services provides supporting features such as deployment configuration and API
56
56
  key management. All API traffic to your gateways stays on your infrastructure.
@@ -5,7 +5,7 @@ sidebar_label: Environment variables
5
5
 
6
6
  For security reasons, your local development doesn't have access to the
7
7
  environment variables that you have configured on the Zuplo Portal. Instead,
8
- your local Zuplo API Gateway will load environment variables from a .env file.
8
+ your local Zuplo Gateway will load environment variables from a .env file.
9
9
 
10
10
  1. Create a .env file in the root of your project.
11
11
  2. Follow the following format
@@ -28,7 +28,7 @@ Zuplo supports the following metrics:
28
28
 
29
29
  - request latency
30
30
  - This measures the total time (in milliseconds) that a request takes once it
31
- has entered the API Gateway. It includes any outbound calls from the
31
+ has entered the Zuplo Gateway. It includes any outbound calls from the
32
32
  gateway.
33
33
  - request content length
34
34
  - The content length of the request as reported by the content-length header.