zuplo 6.70.70 → 6.70.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) 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 +63 -0
  7. package/docs/analytics/reference/metrics-glossary.md +105 -0
  8. package/docs/analytics/reference/url-parameters.md +66 -0
  9. package/docs/analytics/shared-controls.md +121 -0
  10. package/docs/analytics/tabs/agents.md +88 -0
  11. package/docs/analytics/tabs/consumers.md +73 -0
  12. package/docs/analytics/tabs/graphql.md +77 -0
  13. package/docs/analytics/tabs/mcp.md +80 -0
  14. package/docs/analytics/tabs/origins.md +82 -0
  15. package/docs/analytics/tabs/requests.md +96 -0
  16. package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
  17. package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
  18. package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
  19. package/docs/articles/ci-cd-github/pr-preview-environments.mdx +36 -4
  20. package/docs/articles/custom-ci-cd-github.mdx +11 -2
  21. package/docs/articles/monetization/api-access.mdx +184 -0
  22. package/docs/articles/monetization/meters.mdx +4 -4
  23. package/docs/articles/monetization/monetization-policy.md +4 -1
  24. package/docs/articles/monetization/private-plans.md +3 -4
  25. package/docs/articles/monetization/stripe-integration.md +9 -0
  26. package/docs/articles/monetization/subscription-lifecycle.md +12 -11
  27. package/docs/articles/monorepo-deployment.mdx +20 -2
  28. package/docs/cli/deploy.mdx +32 -0
  29. package/docs/cli/deploy.partial.mdx +32 -0
  30. package/docs/concepts/api-keys.md +2 -2
  31. package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
  32. package/docs/dev-portal/zudoku/configuration/search.md +36 -0
  33. package/docs/dev-portal/zudoku/configuration/site.md +38 -0
  34. package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
  35. package/docs/errors/rate-limit-exceeded.mdx +30 -3
  36. package/docs/policies/_index.md +2 -0
  37. package/docs/policies/data-loss-prevention-inbound/doc.md +116 -0
  38. package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
  39. package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
  40. package/docs/policies/data-loss-prevention-outbound/doc.md +116 -0
  41. package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
  42. package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
  43. package/docs/programmable-api/background-dispatcher.mdx +6 -8
  44. package/docs/programmable-api/zone-cache.mdx +1 -1
  45. package/docs/rate-limiting/combining-policies.mdx +293 -0
  46. package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
  47. package/docs/rate-limiting/getting-started.mdx +339 -0
  48. package/docs/rate-limiting/how-it-works.md +225 -0
  49. package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
  50. package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -27
  51. package/package.json +4 -4
  52. package/docs/concepts/rate-limiting.md +0 -246
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: "GraphQL"
3
+ sidebar_label: "GraphQL"
4
+ ---
5
+
6
+ The **GraphQL** tab breaks traffic down by GraphQL operation: the queries,
7
+ mutations, and subscriptions clients send through routes you've marked as
8
+ GraphQL endpoints. Use it to find your most-used operations, separate validation
9
+ and resolver errors, and see how much of each operation's latency falls in your
10
+ upstream resolvers. It's visible when the project proxies a GraphQL API.
11
+
12
+ ## When to use this
13
+
14
+ - Find the highest-volume operations and the ones clients call most.
15
+ - Separate resolver, validation, and auth errors when a GraphQL endpoint
16
+ misbehaves.
17
+ - Compare total round-trip latency against resolver-only time to see whether the
18
+ gateway or the upstream owns a slowdown.
19
+
20
+ ## Summary KPIs
21
+
22
+ | Name | What it measures |
23
+ | ----------------- | --------------------------------------------------------------------------------------------- |
24
+ | **Operations** | Total GraphQL operations in the window. |
25
+ | **Success Rate** | Share of operations that completed without error. Secondary: ok / err split. |
26
+ | **p95 Latency** | Total P95 across operations. Secondary: resolver P95. |
27
+ | **Error Classes** | Total errored operations. Secondary: resolver / validation / auth split (`res · val · auth`). |
28
+
29
+ See [Metrics glossary](../reference/metrics-glossary.md) for error-class and
30
+ resolver-latency definitions.
31
+
32
+ ## Charts
33
+
34
+ **GraphQL Operations Over Time.** Operation volume per interval. Populates once
35
+ a client sends a query, mutation, or subscription.
36
+
37
+ **Operation Types.** A donut splitting operations across **query**,
38
+ **mutation**, and **subscription**.
39
+
40
+ **Latency — Total vs Resolver.** Total P95 and resolver P95 over time, with P50
41
+ total, P95 total, P99 total, and P95 resolver summary cards. _What to look for:_
42
+ a total P95 well above the resolver P95 means the operation spends its time
43
+ outside your resolvers — in parsing, validation, or gateway policies — rather
44
+ than in the upstream.
45
+
46
+ ## Operations table
47
+
48
+ | Column | Notes |
49
+ | -------------------- | ---------------------------------------------- |
50
+ | Operation | The GraphQL operation name. |
51
+ | Type | query, mutation, or subscription. |
52
+ | Operations | Count with an inline volume bar. |
53
+ | Errors | Errored-operation count. |
54
+ | Error Rate | Errors ÷ operations. |
55
+ | Complexity (avg/max) | Average and maximum computed query complexity. |
56
+ | p95 | P95 latency for the operation. |
57
+
58
+ The table is searchable and sortable on any column (default: operations
59
+ descending).
60
+
61
+ ## Filters
62
+
63
+ The filter bar applies. See [Shared controls](../shared-controls.md#filters).
64
+
65
+ ## Troubleshooting
66
+
67
+ **The GraphQL tab is empty.** No GraphQL operations arrived in the selected
68
+ window. Operations appear once a client sends a query, mutation, or subscription
69
+ through a route you've marked as a GraphQL endpoint. See
70
+ [GraphQL on Zuplo](../../articles/graphql.mdx) for how to mark a route.
71
+
72
+ **The tab isn't visible.** Visibility requires at least one route you've marked
73
+ as a GraphQL endpoint. See [GraphQL on Zuplo](../../articles/graphql.mdx).
74
+
75
+ **Total latency is high but resolver latency is low.** The operation spends its
76
+ time outside your resolvers. Check the gateway policies on the GraphQL route —
77
+ parsing, validation, complexity analysis, or auth — rather than the upstream.
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: "MCP"
3
+ sidebar_label: "MCP"
4
+ ---
5
+
6
+ The **MCP** tab shows Model Context Protocol traffic through Zuplo: OAuth and
7
+ auth decisions, virtual-server routing, capability and tool invocations,
8
+ JSON-RPC method usage, and upstream MCP server health. It covers both traffic
9
+ that flows _to_ an MCP fleet through Zuplo's gateway and activity _inside_ MCP
10
+ servers you host on Zuplo. It's visible when the project type is **standard**
11
+ and MCP is in use.
12
+
13
+ ## When to use this
14
+
15
+ - See which virtual servers, capabilities, and tools clients call most, and
16
+ who's calling them.
17
+ - Track auth and policy decision outcomes across OAuth flows.
18
+ - Identify whether failures originate in the gateway, the upstream, or the
19
+ client.
20
+ - Investigate the JSON-RPC error codes clients receive.
21
+
22
+ ## Summary KPIs
23
+
24
+ | Name | What it measures |
25
+ | ----------------------- | -------------------------------------------------------------------------------------------- |
26
+ | **Events** | Total MCP events in the window. |
27
+ | **Success Rate** | Share of events with outcome = success. Secondary: success / error split. |
28
+ | **Client Errors (4xx)** | Count of client-side errors. Secondary: share of all errors. |
29
+ | **Server Errors (5xx)** | Count of server-side errors. Secondary: share of all errors. |
30
+ | **Failure Origins** | Combined gateway + upstream + client failures. Secondary: per-origin split (`gw · up · cl`). |
31
+
32
+ See [Metrics glossary](../reference/metrics-glossary.md) for the failure-origin
33
+ and outcome-class definitions.
34
+
35
+ ## Charts
36
+
37
+ **MCP Events Over Time.** Stacked area showing the top event types over the
38
+ window.
39
+
40
+ **Event Families.** A donut distributing events across families: **Requests**,
41
+ **Capabilities**, and **Auth**.
42
+
43
+ **Latency — Gateway vs Upstream.** Total, gateway, and upstream P95 over time,
44
+ with P50 total, P95 total, P95 gateway, and P95 upstream summary cards. _What to
45
+ look for:_ a P95 that the upstream slice dominates points to a slow MCP backend;
46
+ a gateway-heavy P95 points to policy or auth overhead.
47
+
48
+ ## Breakdown tables
49
+
50
+ | Table | Columns |
51
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
52
+ | Capabilities | Server, Capability, Type, Calls, Client (4xx), Server (5xx), Error Rate, P95. |
53
+ | Consumers | Consumer, Events, Client (4xx), Server (5xx). |
54
+ | Virtual Servers | Virtual Server, Events, Client (4xx), Server (5xx). |
55
+ | Upstream Servers | Upstream, Events, Client (4xx), Server (5xx), P95. |
56
+ | MCP Methods | Method (for example `tools/call`, `tools/list`, `resources/list`, `prompts/list`, `resources/templates/list`), Events. |
57
+ | Clients | Client, Kind (from the `initialize` handshake), Events. |
58
+ | JSON-RPC Error Codes | Code, Errors — the JSON-RPC error codes clients receive. |
59
+ | Failure Origins | Origin (gateway / upstream / client), Errors, Client (4xx), Server (5xx). |
60
+ | Reason Codes | Class, Code, Events, Errors, Client (4xx), Server (5xx). |
61
+
62
+ Most tables sort on any column and show the top values by volume. Click **Show
63
+ more** to load the next page.
64
+
65
+ ## Filters
66
+
67
+ The filter bar applies. See [Shared controls](../shared-controls.md#filters).
68
+
69
+ ## Troubleshooting
70
+
71
+ **The MCP tab is empty.** No MCP events arrived in the selected window. Once a
72
+ client connects and invokes a capability or tool, the dashboard populates.
73
+
74
+ **The tab isn't visible.** Visibility requires project type **standard** with
75
+ MCP in use — either an MCP gateway that routes to upstream servers, or an MCP
76
+ server you host on Zuplo.
77
+
78
+ **Errors show but Failure Origins is empty.** Zuplo classifies failure origins
79
+ server-side from event metadata. Events without a clear origin classification
80
+ land in Errors but in none of the gateway / upstream / client buckets.
@@ -0,0 +1,82 @@
1
+ ---
2
+ title: "Origins"
3
+ sidebar_label: "Origins"
4
+ ---
5
+
6
+ The **Origins** tab shows backend performance: how each upstream host you proxy
7
+ to is performing in terms of volume, error rate, and latency. It's visible when
8
+ the project uses managed-edge origins.
9
+
10
+ ## When to use this
11
+
12
+ - Identify which backend is slow or returning errors.
13
+ - Compare the latency contribution of DNS, TCP, TLS, and application time.
14
+ - Audit traffic distribution across direct origins and service tunnels.
15
+
16
+ ## Summary metrics
17
+
18
+ The header strip shows totals derived from the time series:
19
+
20
+ | Name | What it measures |
21
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
22
+ | Total requests | All requests served against any origin in the window. |
23
+ | 4xx rate | Client error rate across all origins. |
24
+ | 5xx rate | Server error rate across all origins. |
25
+ | Weighted avg latency | Origin response time weighted by request count, so high-traffic origins dominate. See [Metrics glossary](../reference/metrics-glossary.md). |
26
+
27
+ ## Charts
28
+
29
+ **Backend Request Time Series.** Stacked bars by status class, aggregated across
30
+ origins by default. Apply a host filter to scope to one origin.
31
+
32
+ **Backend Latency.** Average and P95 over time. _What to look for:_ a P95 climb
33
+ while the average stays flat usually points to a few slow origins or routes
34
+ inside an otherwise healthy fleet.
35
+
36
+ **Backend Error Rate.** 4xx and 5xx rates over time.
37
+
38
+ **Request Lifecycle.** Stacked time spent in each phase of an origin request:
39
+ **DNS time**, **TCP time**, **TLS time**, and **application time**. A high TLS
40
+ slice indicates handshake overhead; a high application slice indicates the
41
+ origin is slow.
42
+
43
+ ## Tables
44
+
45
+ Two tables sit side by side in a 2-column grid.
46
+
47
+ ### Direct Origins
48
+
49
+ | Column | Notes |
50
+ | --------------- | -------------------------------- |
51
+ | Host | The origin hostname. |
52
+ | Requests | Count with an inline volume bar. |
53
+ | Client Errors | 4xx percentage. |
54
+ | Server Errors | 5xx percentage. |
55
+ | Avg / P95 / P99 | Latency percentiles. |
56
+ | 4xx sparkline | Inline trend over the window. |
57
+ | 5xx sparkline | Inline trend over the window. |
58
+
59
+ Clicking a row toggles a host filter. Click again to remove it.
60
+
61
+ ### Service Tunnels
62
+
63
+ Same columns and behavior as Direct Origins, scoped to tunnel-routed origins.
64
+ The table is hidden when no tunnel traffic is present.
65
+
66
+ ## Filters
67
+
68
+ The filter bar applies, with one exception: `userSub` is not applicable on this
69
+ tab. See [Shared controls](../shared-controls.md#filters).
70
+
71
+ ## Troubleshooting
72
+
73
+ **The Origins tab isn't visible.** It appears only when the project uses
74
+ managed-edge origins. If your project routes traffic differently, the tab is
75
+ hidden.
76
+
77
+ **Service Tunnels table is missing.** That table only renders when at least one
78
+ origin is reached over a service tunnel.
79
+
80
+ **A 5xx spike on one origin doesn't match the Requests tab.** If you've filtered
81
+ the Requests tab to a different route or status class, totals won't match. Clear
82
+ filters or compare with the same filters applied on both tabs.
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: "Requests"
3
+ sidebar_label: "Requests"
4
+ ---
5
+
6
+ The **Requests** tab is the default Analytics overview: every request through
7
+ your gateway in the selected time window, with charts and breakdowns for volume,
8
+ latency, and errors.
9
+
10
+ ## When to use this
11
+
12
+ - Spot-check overall traffic and error rate across a project or the whole
13
+ account.
14
+ - Investigate a spike in 4xx or 5xx responses.
15
+ - Drill from a route, status code, or geographic breakdown into the underlying
16
+ requests.
17
+
18
+ ## Summary KPIs
19
+
20
+ | Name | What it measures | When it's useful |
21
+ | ----------------- | ------------------------------------------------------------- | ----------------------------------------- |
22
+ | **Requests** | Total request count. Secondary value: successful (2xx) count. | Quick health check on volume and success. |
23
+ | **Client Errors** | 4xx rate (4xx ÷ total). Secondary value: raw 4xx count. | Spot bad-input or auth issues. |
24
+ | **Server Errors** | 5xx rate (5xx ÷ total). Secondary value: raw 5xx count. | Spot gateway or upstream failures. |
25
+ | **Avg Latency** | Mean response time. Secondary value: min to max. | Detect broad latency regressions. |
26
+ | **Consumers** | Distinct API consumers (authenticated + anonymous). | Gauge active audience. |
27
+
28
+ See [Metrics glossary](../reference/metrics-glossary.md) for how rates and
29
+ percentiles are computed.
30
+
31
+ ## Charts
32
+
33
+ **Request Time Series.** Stacked bars per interval, broken down by status class
34
+ (2xx / 3xx / 4xx / 5xx). Drag to select a region to zoom; the time range picker
35
+ updates to match.
36
+
37
+ **Request Locations Map.** A world map with a heatmap of request volume by
38
+ location. Shown only when geolocation data is present.
39
+
40
+ **Latency Over Time.** P50, P95, and P99 lines. _What to look for:_ a widening
41
+ gap between P50 and P95 typically signals a tail-latency problem affecting a
42
+ subset of requests.
43
+
44
+ **Error Rate.** 4xx and 5xx rates plotted over time.
45
+
46
+ **Latency Distribution.** A histogram of P10, P50, P90, P95, and P99 buckets.
47
+ Click a band to filter the rest of the tab to requests in that duration range.
48
+
49
+ **Active Instances.** Distinct active edge instances over time. A rough
50
+ indicator of how widely your traffic is distributed across gateway workers.
51
+
52
+ ## Breakdowns
53
+
54
+ Each breakdown shows the top 10 values by request count. Click **Show more** to
55
+ load the next 50.
56
+
57
+ **Primary breakdowns:**
58
+
59
+ - **HTTP Method**
60
+ - **HTTP Status**
61
+ - **Route Path**
62
+
63
+ **Account scope only:**
64
+
65
+ - **Project Name**: click to drill into project-scope analytics.
66
+ - **Deployment Name**: click to drill into a specific deployment.
67
+
68
+ **Secondary breakdowns:**
69
+
70
+ - **Country**, **City**, **Colo**
71
+ - **User Sub**
72
+ - **Client IP**
73
+ - **AS Organization**
74
+
75
+ Clicking any value applies an `equals` filter for that field.
76
+
77
+ ## Filters
78
+
79
+ The full filter bar applies. `originHost` is not applicable on this tab. See
80
+ [Shared controls](../shared-controls.md#filters) for match modes and the filter
81
+ pill UI.
82
+
83
+ ## Troubleshooting
84
+
85
+ **The map is missing.** The Request Locations Map only renders when geolocation
86
+ data is present in the time window. Short windows for low-traffic projects may
87
+ not include any geolocated requests.
88
+
89
+ **Show more doesn't load anything.** You may already be viewing every value for
90
+ that breakdown. Top-10 plus 50 covers up to 60 distinct values; beyond that,
91
+ narrow the time range or add a filter.
92
+
93
+ **My charts look sparse.** If your account is new, the trial banner across the
94
+ top calls this out. Click **View demo →** in the banner to see what a fully
95
+ populated dashboard looks like. See
96
+ [Access and entitlements](../access-and-entitlements.md).
@@ -27,7 +27,9 @@ jobs:
27
27
  run: npm install
28
28
 
29
29
  - name: Deploy to Zuplo
30
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
30
+ run:
31
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "${{
32
+ github.ref_name }}"
31
33
  env:
32
34
  ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}
33
35
  ```
@@ -41,6 +43,13 @@ This workflow:
41
43
 
42
44
  Since this deploys from `main`, it updates your production environment.
43
45
 
46
+ Passing `--environment` is technically optional here — without it, the CLI
47
+ infers the environment name from the checked-out git ref — but inference can
48
+ pick the wrong name in CI (detached HEAD checkouts, commits that exist on more
49
+ than one branch, or `pull_request` merge refs). Passing the branch name
50
+ explicitly makes every workflow deploy a predictable environment. See the
51
+ [deploy command reference](../../cli/deploy.mdx) for details.
52
+
44
53
  ## Next Steps
45
54
 
46
55
  - Add [testing after deployment](./deploy-and-test.mdx)
@@ -34,9 +34,14 @@ jobs:
34
34
  - name: Deploy to Zuplo
35
35
  id: deploy
36
36
  shell: bash
37
+ env:
38
+ # head_ref is the source branch on pull_request events (where the
39
+ # checkout is the pull/<number>/merge ref, not the branch);
40
+ # ref_name covers push events
41
+ ENVIRONMENT: ${{ github.head_ref || github.ref_name }}
37
42
  run: |
38
43
  # Capture deployment output
39
- OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
44
+ OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1)
40
45
  echo "$OUTPUT"
41
46
 
42
47
  # Extract the deployment URL
@@ -53,6 +58,14 @@ This workflow:
53
58
  2. Runs your test suite against the live deployment
54
59
  3. Fails the workflow if any tests fail
55
60
 
61
+ The deploy step passes `--environment` explicitly because this workflow runs on
62
+ both `push` and `pull_request` events. The expression
63
+ `${{ github.head_ref || github.ref_name }}` resolves to the branch name on
64
+ either trigger, so every run for a branch updates the same environment. Without
65
+ it, `pull_request` runs create a second environment named after the PR merge ref
66
+ instead of your branch — see
67
+ [PR Preview Environments](./pr-preview-environments.mdx) for details.
68
+
56
69
  ## Writing Tests
57
70
 
58
71
  Place test files in the `tests` folder with the `.test.ts` extension:
@@ -64,7 +64,9 @@ jobs:
64
64
  run: npm install
65
65
 
66
66
  - name: Deploy to Zuplo
67
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
67
+ run:
68
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "${{
69
+ github.ref_name }}"
68
70
  ```
69
71
 
70
72
  This workflow:
@@ -6,6 +6,19 @@ sidebar_label: PR Preview Environments
6
6
  Give every pull request its own Zuplo environment. Reviewers can test changes
7
7
  against a live API, and environments clean up automatically when PRs close.
8
8
 
9
+ :::caution{title="Pass --environment on pull_request events"}
10
+
11
+ Workflows triggered by `pull_request` check out the pull request **merge ref**
12
+ (`refs/pull/<number>/merge`), not your branch. Without `--environment`, the
13
+ Zuplo CLI derives the environment name from that ref and creates an environment
14
+ named `pull/<number>/merge` instead of one named after your branch. If anything
15
+ else deploys the same branch — a `push`-triggered workflow, the GitHub
16
+ integration, or a local `zuplo deploy` — you end up with two environments and
17
+ two different URLs for the same branch. Always pass `--environment` with the
18
+ source branch name (`github.head_ref`).
19
+
20
+ :::
21
+
9
22
  ```yaml title=".github/workflows/pr-workflow.yaml"
10
23
  name: PR Workflow
11
24
 
@@ -13,6 +26,12 @@ on:
13
26
  pull_request:
14
27
  types: [opened, synchronize, reopened, closed]
15
28
 
29
+ # Runs for the same branch share one queue, so rapid pushes don't race
30
+ # concurrent deploys into the same environment
31
+ concurrency:
32
+ group: zuplo-preview-${{ github.head_ref }}
33
+ cancel-in-progress: true
34
+
16
35
  jobs:
17
36
  deploy-and-test:
18
37
  # Run on PR open/update, not on close
@@ -34,8 +53,12 @@ jobs:
34
53
  - name: Deploy to Zuplo
35
54
  id: deploy
36
55
  shell: bash
56
+ env:
57
+ # The PR's source branch — not the pull/<number>/merge ref that
58
+ # pull_request events check out
59
+ ENVIRONMENT: ${{ github.head_ref }}
37
60
  run: |
38
- OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
61
+ OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1)
39
62
  echo "$OUTPUT"
40
63
  DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
41
64
  echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
@@ -103,12 +126,21 @@ This workflow:
103
126
 
104
127
  ## How It Works
105
128
 
106
- - The environment name comes from the branch name (`feature/auth` becomes
107
- `feature-auth`)
108
- - Each push to the PR updates the same environment
129
+ - The deploy step passes `--environment` with the PR's source branch name
130
+ (`github.head_ref`), so the environment is named after the branch — the same
131
+ name the [GitHub integration](../source-control-setup-github.mdx) would use
132
+ - Each push to the PR updates the same environment, so the environment URL stays
133
+ stable for the life of the branch
109
134
  - Closing the PR (merge or abandon) triggers cleanup
110
135
  - The PR comment lets reviewers quickly access the preview
111
136
 
137
+ A stable environment URL matters whenever an external system references it
138
+ exactly — an OIDC token audience, a webhook registration, or an IP/URL
139
+ allowlist. Capture the URL from the deploy output rather than constructing it
140
+ from the branch name: the URL hostname uses a normalized, truncated form of the
141
+ environment name plus a unique identifier (see
142
+ [Branch-Based Deployments](../branch-based-deployments.mdx)).
143
+
112
144
  ## Next Steps
113
145
 
114
146
  - Add [automatic cleanup on branch delete](./cleanup-on-branch-delete.mdx) as a
@@ -47,8 +47,8 @@ The Zuplo CLI handles deployment, testing, and environment management. Your
47
47
  GitHub Actions workflow orchestrates when these happen:
48
48
 
49
49
  ```bash
50
- # Deploy to Zuplo (environment name from branch or --environment flag)
51
- npx zuplo deploy --api-key "$ZUPLO_API_KEY"
50
+ # Deploy to Zuplo (pass --environment with the branch name see below)
51
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$BRANCH_NAME"
52
52
 
53
53
  # Run tests against any Zuplo environment
54
54
  npx zuplo test --endpoint https://your-env.zuplo.dev
@@ -64,6 +64,15 @@ Capture the deployment URL from the deploy command output to pass to subsequent
64
64
  test steps. The CLI outputs `Deployed to https://...` which you can parse and
65
65
  use throughout your workflow.
66
66
 
67
+ Always pass `--environment` with the branch name when deploying from GitHub
68
+ Actions, so that every workflow run for a branch updates the same environment.
69
+ Without it, the CLI infers the environment name from the checked-out git ref —
70
+ and on `pull_request` events that's the PR merge ref
71
+ (`refs/pull/<number>/merge`), not your branch, which silently creates a second
72
+ environment with a different URL. The expression
73
+ `${{ github.head_ref || github.ref_name }}` resolves to the branch name on both
74
+ `pull_request` and `push` events.
75
+
67
76
  ## Prerequisites
68
77
 
69
78
  1. **Disconnect the GitHub integration** — Custom CI/CD replaces automatic