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