zuplo 6.72.3 → 6.72.7

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 (45) hide show
  1. package/docs/analytics/access-and-entitlements.md +27 -34
  2. package/docs/analytics/overview.md +7 -6
  3. package/docs/analytics/reference/url-parameters.md +2 -10
  4. package/docs/analytics/shared-controls.md +3 -10
  5. package/docs/analytics/tabs/agents.md +3 -3
  6. package/docs/analytics/tabs/requests.md +2 -4
  7. package/docs/articles/connect-to-aws-alb-with-mtls.mdx +89 -13
  8. package/docs/articles/graphql-security.mdx +2 -1
  9. package/docs/articles/graphql.mdx +8 -0
  10. package/docs/articles/opentelemetry.mdx +7 -5
  11. package/docs/articles/securing-backend-mtls.mdx +92 -3
  12. package/docs/articles/troubleshooting-slow-responses.mdx +4 -3
  13. package/docs/articles/troubleshooting.md +3 -1
  14. package/docs/policies/_index.md +2 -0
  15. package/docs/policies/clerk-jwt-auth-inbound/schema.json +3 -3
  16. package/docs/policies/mcp-auth0-oauth-inbound/schema.json +8 -5
  17. package/docs/policies/mcp-clerk-oauth-inbound/schema.json +78 -0
  18. package/docs/policies/mcp-cognito-oauth-inbound/schema.json +78 -0
  19. package/docs/policies/mcp-entra-oauth-inbound/schema.json +78 -0
  20. package/docs/policies/mcp-google-oauth-inbound/schema.json +78 -0
  21. package/docs/policies/mcp-keycloak-oauth-inbound/schema.json +78 -0
  22. package/docs/policies/mcp-logto-oauth-inbound/schema.json +78 -0
  23. package/docs/policies/mcp-oauth-inbound/schema.json +8 -5
  24. package/docs/policies/mcp-okta-oauth-inbound/schema.json +78 -0
  25. package/docs/policies/mcp-onelogin-oauth-inbound/schema.json +78 -0
  26. package/docs/policies/mcp-ping-oauth-inbound/schema.json +78 -0
  27. package/docs/policies/mcp-token-exchange-inbound/doc.md +72 -4
  28. package/docs/policies/mcp-token-exchange-inbound/intro.md +5 -4
  29. package/docs/policies/mcp-token-exchange-inbound/schema.json +277 -2
  30. package/docs/policies/mcp-workos-oauth-inbound/schema.json +78 -0
  31. package/docs/policies/open-id-jwt-auth-inbound/schema.json +3 -3
  32. package/docs/policies/propel-auth-jwt-inbound/schema.json +3 -3
  33. package/docs/policies/upstream-aws-federated-auth-inbound/doc.md +78 -0
  34. package/docs/policies/upstream-aws-federated-auth-inbound/intro.md +20 -0
  35. package/docs/policies/upstream-aws-federated-auth-inbound/schema.json +98 -0
  36. package/docs/policies/upstream-aws-service-auth-inbound/doc.md +89 -0
  37. package/docs/policies/upstream-aws-service-auth-inbound/intro.md +19 -0
  38. package/docs/policies/upstream-aws-service-auth-inbound/schema.json +113 -0
  39. package/docs/policies/upstream-azure-ad-service-auth-inbound/schema.json +4 -4
  40. package/docs/policies/upstream-gcp-federated-auth-inbound/doc.md +2 -2
  41. package/docs/policies/upstream-gcp-federated-auth-inbound/schema.json +4 -4
  42. package/docs/policies/upstream-gcp-service-auth-inbound/doc.md +3 -3
  43. package/docs/policies/upstream-gcp-service-auth-inbound/schema.json +3 -3
  44. package/docs/policies/upstream-zuplo-jwt-auth-inbound/schema.json +19 -0
  45. package/package.json +4 -4
@@ -7,56 +7,49 @@ sidebar_label: "Access & Entitlements"
7
7
 
8
8
  - Confirm whether your account can see analytics.
9
9
  - Find out how many days of history you have access to.
10
- - Understand the trial banner or the demo mode link.
10
+ - Understand the trial banner.
11
11
 
12
- ## Plan requirements
12
+ ## Availability
13
13
 
14
- Analytics must be enabled on your account. Without it, the Analytics page shows
15
- an upsell view with a **Contact Sales** call-to-action and no charts.
14
+ Analytics is included on every plan. Your plan determines how much history you
15
+ can query see [Data retention](#data-retention). If the Analytics page shows
16
+ an upsell view with a **Contact Sales** call-to-action instead of charts, your
17
+ account has analytics disabled — contact your Zuplo account team.
16
18
 
17
19
  ## Free trial
18
20
 
19
- New accounts with analytics enabled get an automatic free trial. The trial:
21
+ Every new account gets an automatic 14-day observability trial. During the
22
+ trial:
20
23
 
21
- - Runs for the same number of days as your account's retention window.
22
- - Shows a banner across the top of the Analytics page: "You're on a {N}-day
23
- preview of Analytics, {N} days left."
24
- - Includes two call-to-actions: **View demo →** (loads the dashboard with sample
25
- data) and **Contact sales**.
24
+ - You get longer history windows for analytics, logs, and traces than your plan
25
+ includes. Accounts on self-serve plans get the Builder/Business windows (7
26
+ days of analytics, 1 day of logs and traces); Enterprise trial accounts get
27
+ the Enterprise windows. See [Data retention](#data-retention).
28
+ - A banner appears across the top of the Observability tabs: "You're on a 14-day
29
+ trial of Zuplo Observability — {N} days left", with a **Contact sales** link.
26
30
 
27
- Accounts on the legacy analytics version are not eligible for the trial. They
28
- continue to use the previous experience.
31
+ When the trial ends, your plan's own history windows apply.
29
32
 
30
- :::note
31
-
32
- The trial banner notes that the charts may look sparse if your account hasn't
33
- yet generated much traffic. Use **View demo →** to see what a fully populated
34
- dashboard looks like.
33
+ ## Data retention
35
34
 
36
- :::
35
+ Each account has history windows for analytics, logs, and traces. The windows
36
+ depend on your plan:
37
37
 
38
- ## Data retention
38
+ | Plan | Analytics history | Log & trace history |
39
+ | ------------------ | ----------------- | ------------------- |
40
+ | Free | 1 day | Live (15 minutes) |
41
+ | Builder / Business | 7 days | 1 day |
42
+ | Enterprise | 30 days | 3 days |
39
43
 
40
- Each account has an analytics history window measured in days. The window
41
- controls:
44
+ The analytics window controls:
42
45
 
43
46
  - How far back you can scroll using the time-range picker.
44
47
  - Which presets in the picker are available. Presets longer than your window are
45
- locked with an **Upgrade for [preset]** tooltip.
48
+ locked and show an **Upgrade** badge; hover one for details.
46
49
  - The maximum start and end values when you pick a custom range.
47
50
 
48
- If you need a longer window, contact your Zuplo account team.
49
-
50
- ## Demo mode
51
-
52
- Append `?demo=true` to the Analytics URL, or click **View demo →** in the trial
53
- banner, to switch into demo mode. In demo mode:
54
-
55
- - Charts and tables are populated with synthetic sample data.
56
- - A persistent banner reads: "You're viewing the Analytics demo with sample
57
- data. Your real analytics aren't shown here."
58
-
59
- Remove the `demo` parameter from the URL to return to your real data.
51
+ If you need a longer window, contact your Zuplo account team — custom retention
52
+ is available on Enterprise plans.
60
53
 
61
54
  ## Scope: account vs project
62
55
 
@@ -32,7 +32,7 @@ Analytics lives in the **Observability** tab of the Zuplo Portal, alongside
32
32
  ## What's in this section
33
33
 
34
34
  - [Access and entitlements](./access-and-entitlements.md): plans, free trial,
35
- demo mode, retention.
35
+ retention.
36
36
  - [Shared controls](./shared-controls.md): time range, filters, environment
37
37
  selector, banners, URL state.
38
38
  - Sections:
@@ -56,12 +56,13 @@ You'll see a subset of sections depending on your plan and project setup:
56
56
 
57
57
  | Section | When it appears |
58
58
  | --------- | ---------------------------------------------------------- |
59
- | Requests | All accounts with analytics enabled. |
59
+ | Requests | All accounts. |
60
60
  | Origins | The project uses managed-edge origins. |
61
- | Consumers | All accounts with analytics enabled. |
62
- | Agents | All accounts with analytics enabled. |
61
+ | Consumers | All accounts. |
62
+ | Agents | All accounts. |
63
63
  | MCP | The project type is **standard** and the project uses MCP. |
64
64
  | GraphQL | The project proxies a GraphQL API. |
65
65
 
66
- If you don't see Analytics at all, your account likely doesn't have analytics
67
- enabled. See [Access and entitlements](./access-and-entitlements.md).
66
+ Analytics is included on every plan. If you don't see Analytics at all, your
67
+ account may have it disabled — see
68
+ [Access and entitlements](./access-and-entitlements.md).
@@ -8,7 +8,7 @@ view.
8
8
 
9
9
  ## When to use this
10
10
 
11
- - Build a permalink to a specific time window, filter set, or demo view.
11
+ - Build a permalink to a specific time window or filter set.
12
12
  - Embed an Analytics link in a runbook, postmortem, or dashboard.
13
13
  - Understand what each query parameter does.
14
14
 
@@ -16,11 +16,9 @@ view.
16
16
 
17
17
  | Parameter | Example | Effect |
18
18
  | -------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
19
- | `time` | `?time=7d` | Apply a preset. Values: `1h`, `6h`, `24h`, `3d`, `7d`, `14d`, `28d`, `60d`, `90d`. |
19
+ | `time` | `?time=7d` | Apply a preset. Values: `1h`, `6h`, `24h`, `3d`, `7d`, `14d`, `28d`, `30d`, `60d`, `90d`. |
20
20
  | `start`, `end` | `?start=2026-05-01T00:00:00Z&end=2026-05-15T00:00:00Z` | Custom range as ISO-8601 datetimes. Overrides `time` when both are present. |
21
21
  | `filter` | `?filter=httpStatus:class:5xx` | Add a filter as `<field>:<matchmode>:<value>`. Repeat the parameter for multiple filters. |
22
- | `demo` | `?demo=true` | Demo mode (sample data instead of your real analytics). |
23
- | `preview` | `?preview=1` | Legacy preview mode. |
24
22
 
25
23
  ## Match modes for `filter`
26
24
 
@@ -48,12 +46,6 @@ Custom range with two filters:
48
46
  ?start=2026-05-01T00:00:00Z&end=2026-05-08T00:00:00Z&filter=country:equals:US&filter=httpMethod:equals:POST
49
47
  ```
50
48
 
51
- Open the demo:
52
-
53
- ```
54
- ?demo=true
55
- ```
56
-
57
49
  ## Sharing
58
50
 
59
51
  The recipient sees the same view, provided they have access to the project or
@@ -18,7 +18,7 @@ State persists to the URL so you can share or bookmark any view.
18
18
  The time range picker controls every chart, table, and KPI in the active
19
19
  section.
20
20
 
21
- **Presets.** Last 1h, 6h, 24h, 3d, 7d, 14d, 28d, 60d, 90d.
21
+ **Presets.** Last 1h, 6h, 24h, 3d, 7d, 14d, 28d, 30d, 60d, 90d.
22
22
 
23
23
  **Custom range.** Use the datetime-local inputs for **Start** and **End**. Both
24
24
  fields are clamped to your account's retention window.
@@ -80,8 +80,6 @@ There's no separate share button.
80
80
  | `time` | `?time=7d` | Apply a preset. |
81
81
  | `start`, `end` | `?start=2026-05-01T00:00:00Z&end=2026-05-15T00:00:00Z` | Custom range. Overrides `time`. |
82
82
  | `filter` | `?filter=httpStatus:class:5xx` | Add a filter. Repeat the parameter for multiple values. |
83
- | `demo` | `?demo=true` | Demo mode (sample data). |
84
- | `preview` | `?preview=1` | Legacy preview mode. |
85
83
 
86
84
  See [URL parameters](./reference/url-parameters.md) for the full reference.
87
85
 
@@ -94,13 +92,8 @@ a refetch.
94
92
 
95
93
  ## Banners
96
94
 
97
- Banners appear at the top of the page in this priority order:
98
-
99
- 1. **Preview banner**: when `preview=1` is set. Indicates legacy preview mode.
100
- 2. **Demo banner**: when `demo=true` is set. Reminds you sample data is shown
101
- instead of your real analytics.
102
- 3. **Trial banner**: for new accounts with analytics. Shows days remaining and
103
- offers **View demo →** and **Contact Sales**.
95
+ During the 14-day observability trial, a banner appears at the top of the page
96
+ showing the days remaining, with a **Contact sales** link.
104
97
 
105
98
  ## Loading and empty states
106
99
 
@@ -74,9 +74,9 @@ The filter bar applies. `originHost` is not applicable here. See
74
74
  ## Troubleshooting
75
75
 
76
76
  **The Agents section is empty.** Either no classified agents called your gateway
77
- in the window, or your retention window doesn't yet include any agent traffic.
78
- Try the demo with **View demo →** in the trial banner to see what a populated
79
- view looks like. See [Access and entitlements](../access-and-entitlements.md).
77
+ in the window, or your
78
+ [retention window](../access-and-entitlements.md#data-retention) doesn't yet
79
+ include any agent traffic.
80
80
 
81
81
  **I see a known agent in my logs but not here.** The classifier is conservative;
82
82
  it labels traffic that clearly matches a known agent fingerprint. Generic SDK
@@ -91,7 +91,5 @@ not include any geolocated requests.
91
91
  that breakdown. Top-10 plus 50 covers up to 60 distinct values; beyond that,
92
92
  narrow the time range or add a filter.
93
93
 
94
- **My charts look sparse.** If your account is new, the trial banner across the
95
- top calls this out. Click **View demo →** in the banner to see what a fully
96
- populated dashboard looks like. See
97
- [Access and entitlements](../access-and-entitlements.md).
94
+ **My charts look sparse.** New accounts may not have generated much traffic yet.
95
+ Charts fill in as requests flow through your gateway.
@@ -20,9 +20,9 @@ present an X.509 certificate that chains to a Certificate Authority (CA) in the
20
20
  ALB's trust store. Zuplo presents that client certificate on each outbound
21
21
  request, and the ALB rejects anything that can't.
22
22
 
23
- This guide covers the Zuplo side of that connection: uploading a client
24
- certificate and presenting it on requests to the ALB. It does **not** cover
25
- configuring the ALB itself — for that, follow the AWS documentation linked in
23
+ This guide covers uploading a client certificate to Zuplo and presenting it on
24
+ requests to the ALB. It does **not** cover configuring the ALB itself — for
25
+ that, follow the AWS documentation linked in
26
26
  [Configure the ALB](#1-configure-the-alb).
27
27
 
28
28
  ## How it works
@@ -64,10 +64,13 @@ For background on the gateway-to-origin direction in general, see
64
64
 
65
65
  Before you begin, you need:
66
66
 
67
- - A client certificate and private key (PEM-encoded) issued by a CA. The same CA
68
- must be uploaded to the ALB's trust store.
67
+ - A PEM-encoded client certificate (`client-cert.pem`), matching private key
68
+ (`client-key.pem`), and public CA certificate or bundle (`client-ca.pem`). The
69
+ same CA must be in the ALB's trust store.
69
70
  - An AWS Application Load Balancer with an HTTPS listener you can configure for
70
71
  mTLS.
72
+ - The AWS CLI installed and authenticated with permission to inspect the ALB's
73
+ listener and trust store.
71
74
  - The [Zuplo CLI](../cli/overview.mdx) installed and authenticated.
72
75
 
73
76
  ## 1/ Configure the ALB
@@ -93,7 +96,79 @@ edge.
93
96
 
94
97
  :::
95
98
 
96
- ## 2/ Upload your client certificate to Zuplo
99
+ ## 2/ Validate the certificate and ALB trust
100
+
101
+ Confirm that the listener uses verify mode and capture its trust store ARN:
102
+
103
+ ```bash
104
+ aws elbv2 describe-listeners \
105
+ --listener-arns <HTTPS_LISTENER_ARN> \
106
+ --query 'Listeners[0].MutualAuthentication' \
107
+ --output json
108
+
109
+ TRUST_STORE_ARN=$(aws elbv2 describe-listeners \
110
+ --listener-arns <HTTPS_LISTENER_ARN> \
111
+ --query 'Listeners[0].MutualAuthentication.TrustStoreArn' \
112
+ --output text)
113
+
114
+ aws elbv2 describe-trust-stores \
115
+ --trust-store-arns "$TRUST_STORE_ARN" \
116
+ --query 'TrustStores[0].{Name:Name,Status:Status,CACertificates:NumberOfCaCertificates}' \
117
+ --output table
118
+ ```
119
+
120
+ The listener mode should be `verify`, and the trust store status should be
121
+ `ACTIVE`.
122
+
123
+ Inspect the client certificate and verify its path against the CA certificate:
124
+
125
+ ```bash
126
+ openssl x509 \
127
+ -in client-cert.pem \
128
+ -noout \
129
+ -subject \
130
+ -issuer \
131
+ -dates \
132
+ -purpose
133
+
134
+ openssl verify \
135
+ -verbose \
136
+ -purpose sslclient \
137
+ -x509_strict \
138
+ -show_chain \
139
+ -CAfile client-ca.pem \
140
+ client-cert.pem
141
+ ```
142
+
143
+ `SSL client` should report `Yes`, and verification should return
144
+ `client-cert.pem: OK`. Also confirm that the certificate and private key match:
145
+
146
+ ```bash
147
+ CERT_KEY_HASH=$(openssl x509 -in client-cert.pem -noout -pubkey | openssl sha256)
148
+ PRIVATE_KEY_HASH=$(openssl pkey -in client-key.pem -pubout | openssl sha256)
149
+
150
+ test "$CERT_KEY_HASH" = "$PRIVATE_KEY_HASH" &&
151
+ echo "Certificate and private key match" ||
152
+ echo "ERROR: Certificate and private key do not match"
153
+ ```
154
+
155
+ For the strongest offline check, run `openssl verify` with the exact CA bundle
156
+ uploaded to the ALB trust store instead of `client-ca.pem`.
157
+
158
+ If the ALB is reachable, test the TLS handshake directly:
159
+
160
+ ```bash
161
+ curl --verbose \
162
+ --cert client-cert.pem \
163
+ --key client-key.pem \
164
+ "https://<ALB_HOSTNAME>/<TEST_PATH>"
165
+ ```
166
+
167
+ Use a hostname covered by the ALB's server certificate. Any HTTP response,
168
+ including a `401`, `403`, or `404`, shows that the TLS handshake completed; a
169
+ client certificate failure occurs before an HTTP response is produced.
170
+
171
+ ## 3/ Upload your client certificate to Zuplo
97
172
 
98
173
  Use the Zuplo CLI to upload the client certificate and private key to your
99
174
  project. The CA that issued this certificate must already be in the ALB's trust
@@ -119,7 +194,7 @@ when you create the certificate.
119
194
 
120
195
  :::
121
196
 
122
- ## 3/ Present the certificate on requests to the ALB
197
+ ## 4/ Present the certificate on requests to the ALB
123
198
 
124
199
  Reference the uploaded certificate by name when the gateway forwards requests to
125
200
  the ALB. Use the ALB's DNS name (or a custom domain pointed at it) as the
@@ -162,7 +237,7 @@ export default async function (request: ZuploRequest, context: ZuploContext) {
162
237
  }
163
238
  ```
164
239
 
165
- ## 4/ Use environment variables across environments
240
+ ## 5/ Use environment variables across environments
166
241
 
167
242
  To use a different certificate per environment, store the certificate name in an
168
243
  [environment variable](./environment-variables.mdx) and reference it with the
@@ -226,11 +301,12 @@ received — usually a TLS handshake problem. Confirm that:
226
301
 
227
302
  ### The ALB rejects the certificate
228
303
 
229
- If the handshake completes but the ALB returns a `403`, the certificate is being
230
- presented but not trusted. Re-check the ALB trust store and confirm the listener
231
- is in **verify** mode, not passthrough. If your client certificate is issued by
232
- an intermediate CA, make sure the trust store contains the full chain back to
233
- the root.
304
+ If the TLS handshake fails, re-check the ALB trust store and confirm the
305
+ listener is in **verify** mode, not passthrough. If your client certificate is
306
+ issued by an intermediate CA, make sure the trust store contains the CA chain
307
+ required to validate the certificate. ALB connection logs can identify errors
308
+ such as `ClientCertUntrusted`, `ClientCertPurposeInvalid`, `ClientCertExpired`,
309
+ or `ClientCertNotYetValid`.
234
310
 
235
311
  ### No certificate appears to be sent
236
312
 
@@ -180,7 +180,8 @@ outbound filter policy goes in the `outbound` array:
180
180
  ## Example repository
181
181
 
182
182
  For a complete, runnable setup, see the
183
- [GraphQL API with Zuplo example repository](https://github.com/zuplo/zuplo-graphql-example).
183
+ [GraphQL API with Zuplo example repository](https://github.com/zuplo-samples/graphql).
184
+ You can [deploy it to your own account in one click](./graphql.mdx).
184
185
 
185
186
  ## Next steps
186
187
 
@@ -12,6 +12,14 @@ Zuplo has rich support for GraphQL. Pass your requests through the gateway,
12
12
  attach policies, track operations with analytics, and publish documentation for
13
13
  your schema in the Dev Portal. This guide walks you through setting it up.
14
14
 
15
+ <ZupIt repoUrl="https://github.com/zuplo-samples/graphql">
16
+
17
+ Want to see it working first? This example proxies a public GraphQL API with
18
+ edge caching, query depth limiting, and analytics already wired up — deploy it
19
+ to your own Zuplo account in one click.
20
+
21
+ </ZupIt>
22
+
15
23
  :::tip{title="TL;DR"}
16
24
 
17
25
  - [ ] Proxy your GraphQL endpoint through a POST `/graphql` route with the URL
@@ -7,10 +7,9 @@ Zuplo ships with an OpenTelemetry plugin (`@zuplo/otel`) that instruments your
7
7
  API and exports traces and logs in OpenTelemetry format. The quickest way to use
8
8
  it is Zuplo's built-in tracing: add the plugin and your traces are stored by
9
9
  Zuplo and shown in the portal's **Observability** tab, with no collector to run
10
- or backend to host. You can also export to your own OpenTelemetry backend, or to
11
- both at once.
12
-
13
- <EnterpriseFeature name="OpenTelemetry" />
10
+ or backend to host. Built-in tracing is available on every plan. You can also
11
+ export to your own OpenTelemetry backend (an Enterprise feature), or to both at
12
+ once.
14
13
 
15
14
  ## Tracing
16
15
 
@@ -68,7 +67,8 @@ Deploy your project and open the **Observability** tab to see traces.
68
67
  Each trace is tagged with the account, project, deployment, and environment it
69
68
  ran in, plus the request ID (the `zp-rid` value) that also appears on your
70
69
  [logs](#logging), so you can move between a request's logs and its trace. How
71
- long traces are kept depends on your plan.
70
+ long traces are kept depends on your plan — see
71
+ [data retention](../analytics/access-and-entitlements.md#data-retention).
72
72
 
73
73
  :::note
74
74
 
@@ -81,6 +81,8 @@ still run.
81
81
 
82
82
  ### Export to Your Own Backend
83
83
 
84
+ <EnterpriseFeature name="Exporting to your own OpenTelemetry backend" />
85
+
84
86
  To send traces to an OpenTelemetry service such as
85
87
  [Honeycomb](https://honeycomb.io), [Middleware](https://middleware.io/),
86
88
  [Dynatrace](https://dynatrace.com), [Jaeger](https://www.jaegertracing.io/), or
@@ -30,11 +30,38 @@ gateways, and Zuplo can verify it's connecting to the correct backend service.
30
30
 
31
31
  Before you begin, you need:
32
32
 
33
- - A client certificate and private key generated from a Certificate Authority
34
- (CA) that your backend trusts
33
+ - A PEM-encoded client certificate and matching private key generated from a
34
+ Certificate Authority (CA) that your backend trusts
35
35
  - Your backend service configured to require and validate client certificates
36
36
  - The Zuplo CLI installed (see [CLI documentation](../cli/overview.mdx))
37
37
 
38
+ ## Client certificate requirements
39
+
40
+ The certificate uploaded to Zuplo is the client, or leaf, certificate that the
41
+ gateway presents to your backend. It should have the following properties:
42
+
43
+ - X.509 version 3
44
+ - `Basic Constraints: critical, CA:FALSE`
45
+ - `Key Usage: critical, Digital Signature`
46
+ - `Extended Key Usage: TLS Web Client Authentication` (`clientAuth`)
47
+ - An RSA key between 2048 and 8192 bits, or an ECDSA key using P-256, P-384, or
48
+ P-521
49
+ - A SHA-256 or stronger signature
50
+ - A subject that uniquely identifies the gateway credential
51
+ - A validity period appropriate for your certificate rotation policy
52
+
53
+ The issuing CA certificate must be X.509 version 3 with
54
+ `Basic Constraints: critical, CA:TRUE` and a key usage that includes
55
+ `Certificate Sign` and `CRL Sign`. Your backend's trust store must contain the
56
+ CA certificates needed to build a path from the client certificate to a trusted
57
+ root. When the trusted CA directly signs the client certificate, you do not need
58
+ a client certificate-chain bundle. If an intermediate CA signs it, follow your
59
+ backend's requirements for presenting and trusting the intermediate chain.
60
+
61
+ The private key must match the public key in the client certificate. Keep it
62
+ secret and limit access to it. The key must be unencrypted when uploaded to
63
+ Zuplo so the gateway can use it without a passphrase.
64
+
38
65
  ## 1/ Upload Your Certificate
39
66
 
40
67
  Use the Zuplo CLI to upload your client certificate and private key to your
@@ -229,10 +256,72 @@ For local development, consider:
229
256
 
230
257
  ### Certificate Validation Errors
231
258
 
232
- If your backend rejects the certificate, verify:
259
+ Inspect the certificate and confirm its subject, issuer, validity, and client
260
+ authentication purpose:
261
+
262
+ ```bash
263
+ openssl x509 \
264
+ -in client.crt \
265
+ -noout \
266
+ -subject \
267
+ -issuer \
268
+ -serial \
269
+ -dates \
270
+ -purpose
271
+ ```
272
+
273
+ `SSL client` should report `Yes`. To inspect all extensions, run
274
+ `openssl x509 -in client.crt -noout -text` and confirm the certificate reports
275
+ `CA:FALSE`, `Digital Signature`, and `TLS Web Client Authentication`.
276
+
277
+ Check that the certificate will remain valid for at least seven days:
278
+
279
+ ```bash
280
+ openssl x509 -in client.crt -noout -checkend 604800
281
+ ```
282
+
283
+ Validate the certificate path against the same CA bundle your backend trusts:
284
+
285
+ ```bash
286
+ openssl verify \
287
+ -verbose \
288
+ -purpose sslclient \
289
+ -x509_strict \
290
+ -show_chain \
291
+ -CAfile backend-client-ca-bundle.pem \
292
+ client.crt
293
+ ```
294
+
295
+ The expected result is `client.crt: OK`. Common errors include:
296
+
297
+ - `unable to get local issuer certificate`: The issuing CA or an intermediate CA
298
+ is missing from the backend's trust bundle.
299
+ - `unsupported certificate purpose`: The client certificate is missing the
300
+ `clientAuth` extended key usage.
301
+ - `certificate has expired`: The client certificate is outside its validity
302
+ period.
303
+ - `invalid CA certificate`: An issuing certificate is missing valid CA
304
+ extensions.
305
+ - `key usage does not include certificate signing`: An issuing CA does not have
306
+ the `keyCertSign` key usage.
307
+
308
+ Confirm that the certificate and private key contain the same public key:
309
+
310
+ ```bash
311
+ CERT_KEY_HASH=$(openssl x509 -in client.crt -noout -pubkey | openssl sha256)
312
+ PRIVATE_KEY_HASH=$(openssl pkey -in client.key -pubout | openssl sha256)
313
+
314
+ test "$CERT_KEY_HASH" = "$PRIVATE_KEY_HASH" &&
315
+ echo "Certificate and private key match" ||
316
+ echo "ERROR: Certificate and private key do not match"
317
+ ```
318
+
319
+ If your backend still rejects the certificate, verify:
233
320
 
234
321
  - The certificate is signed by a CA that your backend trusts
235
322
  - The certificate hasn't expired
323
+ - The certificate has the required key usage and extended key usage
324
+ - The certificate and private key match
236
325
  - The certificate name in your code matches the uploaded certificate name
237
326
 
238
327
  ### Connection Failures
@@ -240,9 +240,10 @@ For the most detailed view of where time is spent in your request pipeline,
240
240
  enable [OpenTelemetry tracing](./opentelemetry.mdx). The OpenTelemetry plugin
241
241
  automatically instruments your API and provides span-level timing for each stage
242
242
  of the request lifecycle — including inbound policies, the handler, outbound
243
- policies, and any subrequests made via `fetch` in custom code.
244
-
245
- <EnterpriseFeature name="OpenTelemetry" />
243
+ policies, and any subrequests made via `fetch` in custom code. Built-in tracing
244
+ is available on every plan; see
245
+ [data retention](../analytics/access-and-entitlements.md#data-retention) for how
246
+ much trace history you can query.
246
247
 
247
248
  With tracing enabled, you can see exactly how long each policy and handler takes
248
249
  to execute, making it straightforward to identify which component is adding
@@ -179,7 +179,9 @@ const response = await fetch("https://api.example.com/data", {
179
179
  The Zuplo Portal provides real-time log viewing for deployed environments. Open
180
180
  the **Observability** tab in your project — the **Logs** view opens by default —
181
181
  then use the **Environment** filter to select the deployed environment and see
182
- live request logs and any messages logged with `context.log`.
182
+ live request logs and any messages logged with `context.log`. How far back you
183
+ can search depends on your plan — see
184
+ [Access and entitlements](../analytics/access-and-entitlements.md#data-retention).
183
185
 
184
186
  ### Using context.log
185
187
 
@@ -104,6 +104,8 @@
104
104
  | traffic-splitting-inbound | Traffic Splitting | Splits traffic randomly across a set of weighted base paths. On each request one base path is selected (weighted by `weight`) and written to the request custom context at `customOutputProperty`. Reference it from a later URL Rewrite `rewritePattern` or URL Forward `baseUrl`, e.g. `${context.custom.trafficSplitting.basePath}`. | api-gateway |
105
105
  | transform-body-inbound | Transform Request Body | Transform the body of an incoming request. | api-gateway |
106
106
  | transform-body-outbound | Transform Response Body | Transform the body of an outgoing response. | api-gateway |
107
+ | upstream-aws-federated-auth-inbound | Upstream AWS Federated Auth | Resolves AWS credentials with STS AssumeRoleWithWebIdentity using Zuplo's ambient OIDC identity — no AWS keys are stored anywhere. The role's trust policy must trust the Zuplo OIDC identity provider. Resolved credentials are registered on the request context for the AWS Lambda handler and custom code to sign upstream requests with `AwsClient.fromContext`. This policy does not itself sign or forward the request. | api-gateway |
108
+ | upstream-aws-service-auth-inbound | Upstream AWS Service Auth | Resolves AWS credentials from static access keys (optionally exchanged for an IAM role's temporary credentials via STS AssumeRole) and registers them on the request context. The AWS Lambda handler and custom code read them with `AwsClient.fromContext` to sign upstream requests. This policy does not itself sign or forward the request. | api-gateway |
107
109
  | upstream-azure-ad-service-auth-inbound | Upstream Azure AD Service Auth | Uses Azure Active Directory to add an Authorization header to the request in order to authenticate requests using Azure identity. | api-gateway |
108
110
  | upstream-firebase-admin-auth-inbound | Upstream Firebase Admin Auth | Creates a Firebase Admin token and attaches it to the outgoing request. Useful when calling Firebase services as an administrator. | api-gateway |
109
111
  | upstream-firebase-user-auth-inbound | Upstream Firebase User Auth | Creates a Firebase custom user token and attaches it to the outgoing request. Useful when calling Firebase services as user. | api-gateway |
@@ -42,8 +42,8 @@
42
42
  },
43
43
  "frontendApiUrl": {
44
44
  "type": "string",
45
- "examples": ["https://sensible-skunk-49.clerk.accounts.dev"],
46
- "description": "Your Clerk frontend api url, i.e. `https://sensible-skunk-49.clerk.accounts.dev`. Can be found in the Clerk portal: https://dashboard.clerk.com/last-active?path=api-keys."
45
+ "examples": ["https://verb-noun-00.clerk.accounts.dev"],
46
+ "description": "Your Clerk Frontend API URL, i.e. `https://verb-noun-00.clerk.accounts.dev`. Can be found in the Clerk portal: https://dashboard.clerk.com/last-active?path=api-keys."
47
47
  },
48
48
  "oAuthResourceMetadataEnabled": {
49
49
  "type": "boolean",
@@ -59,7 +59,7 @@
59
59
  "module": "$import(@zuplo/runtime)",
60
60
  "options": {
61
61
  "allowUnauthenticatedRequests": false,
62
- "frontendApiUrl": "https://sensible-skunk-49.clerk.accounts.dev",
62
+ "frontendApiUrl": "https://verb-noun-00.clerk.accounts.dev",
63
63
  "oAuthResourceMetadataEnabled": false
64
64
  }
65
65
  }
@@ -100,7 +100,7 @@
100
100
  {
101
101
  "type": "object",
102
102
  "additionalProperties": false,
103
- "required": ["enabled", "trustedIssuers"],
103
+ "required": ["enabled"],
104
104
  "properties": {
105
105
  "enabled": {
106
106
  "const": true,
@@ -109,21 +109,24 @@
109
109
  "trustedIssuers": {
110
110
  "type": "array",
111
111
  "minItems": 1,
112
- "description": "Trusted ID-JAG issuers. These values are never published in OAuth metadata.",
112
+ "description": "Trusted ID-JAG issuers. These values are never published in OAuth metadata. Omit to trust this policy's browser-login IdP.",
113
113
  "items": {
114
114
  "type": "object",
115
115
  "additionalProperties": false,
116
- "required": ["issuer", "jwksUrl"],
116
+ "dependentRequired": {
117
+ "issuer": ["jwksUrl"],
118
+ "jwksUrl": ["issuer"]
119
+ },
117
120
  "properties": {
118
121
  "issuer": {
119
122
  "type": "string",
120
123
  "format": "uri",
121
- "description": "Exact issuer URL expected in the ID-JAG iss claim."
124
+ "description": "Exact issuer URL expected in the ID-JAG iss claim. Provide together with jwksUrl, or omit both to default to this policy's browser-login IdP."
122
125
  },
123
126
  "jwksUrl": {
124
127
  "type": "string",
125
128
  "format": "uri",
126
- "description": "JWKS URL used to verify ID-JAG signatures from this issuer."
129
+ "description": "JWKS URL used to verify ID-JAG signatures from this issuer. Provide together with issuer, or omit both to default to this policy's browser-login IdP."
127
130
  },
128
131
  "expectedClientIds": {
129
132
  "type": "array",