zuplo 6.70.26 → 6.70.28
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/articles/accounts/switching-between-accounts.mdx +160 -0
- package/docs/articles/monetization/api-access.mdx +0 -130
- package/docs/articles/monetization/going-to-production.mdx +516 -0
- package/docs/articles/monetization/index.mdx +1 -0
- package/docs/articles/monetization/monetization-policy.md +1 -4
- package/docs/articles/monetization/plan-examples.mdx +34 -6
- package/docs/articles/monetization/stripe-integration.md +2 -12
- package/docs/articles/monetization/troubleshooting.md +45 -28
- package/package.json +4 -4
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Switching Between Accounts
|
|
3
|
+
sidebar_label: Switching Accounts
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
If you belong to more than one Zuplo account (for example, your own personal
|
|
7
|
+
account plus an account you were invited to join), you can switch between them
|
|
8
|
+
in the Zuplo Portal without signing out.
|
|
9
|
+
|
|
10
|
+
This guide covers accepting an invitation, finding the account switcher, and
|
|
11
|
+
troubleshooting common issues when you can't see an account you expect to have
|
|
12
|
+
access to.
|
|
13
|
+
|
|
14
|
+
:::caution{title="Sign in with the same identity"}
|
|
15
|
+
|
|
16
|
+
The invitation must be accepted using the same identity (email address and
|
|
17
|
+
sign-in method) it was sent to. Zuplo treats Google sign-in, GitHub sign-in, and
|
|
18
|
+
email-password as separate identities, even when they share an email address. If
|
|
19
|
+
you click an invitation while signed in with a different identity, the
|
|
20
|
+
invitation does not apply.
|
|
21
|
+
|
|
22
|
+
:::
|
|
23
|
+
|
|
24
|
+
## Accept an account invitation
|
|
25
|
+
|
|
26
|
+
When an account admin invites you, Zuplo sends an invitation email to the
|
|
27
|
+
address they entered.
|
|
28
|
+
|
|
29
|
+
<Stepper>
|
|
30
|
+
|
|
31
|
+
1. Open the invitation email and click the link to accept the invitation.
|
|
32
|
+
1. If you are not already signed in to the Zuplo Portal, sign in with the **same
|
|
33
|
+
email address and method** the invitation was sent to. See
|
|
34
|
+
[I accepted the invitation but I don't see the account](#i-accepted-the-invitation-but-i-dont-see-the-account)
|
|
35
|
+
if you hit a mismatch.
|
|
36
|
+
1. After signing in, the invited account appears in the **Switch Account**
|
|
37
|
+
submenu of your avatar dropdown.
|
|
38
|
+
|
|
39
|
+
</Stepper>
|
|
40
|
+
|
|
41
|
+
## Switch accounts in the portal
|
|
42
|
+
|
|
43
|
+
Once you belong to multiple accounts, switch between them from the avatar menu.
|
|
44
|
+
|
|
45
|
+
<Stepper>
|
|
46
|
+
|
|
47
|
+
1. Click your avatar in the **top-right corner** of the portal. The currently
|
|
48
|
+
active account name appears at the top of the dropdown.
|
|
49
|
+
1. Hover over **Switch Account** to open the submenu, which lists every account
|
|
50
|
+
you belong to.
|
|
51
|
+
1. Select the account you want to switch to.
|
|
52
|
+
1. The portal reloads in the context of the selected account, showing that
|
|
53
|
+
account's projects, settings, and resources.
|
|
54
|
+
|
|
55
|
+
</Stepper>
|
|
56
|
+
|
|
57
|
+
After switching, all navigation within the portal (project lists, account
|
|
58
|
+
settings, billing, logs) reflects the selected account.
|
|
59
|
+
|
|
60
|
+
## How multi-account membership works
|
|
61
|
+
|
|
62
|
+
When you first sign up for Zuplo, an account is automatically created for you.
|
|
63
|
+
An admin on another account can then invite you by opening their avatar menu,
|
|
64
|
+
selecting **Account Settings → Members**, clicking **Invite to account**, and
|
|
65
|
+
entering your email address and role. Once you accept, you become a member of
|
|
66
|
+
that account in addition to your own.
|
|
67
|
+
|
|
68
|
+
Each account is independent. Projects, billing, custom domains, and API keys all
|
|
69
|
+
belong to a specific account. When you switch accounts in the portal, you see
|
|
70
|
+
only the resources that belong to the account you switched into.
|
|
71
|
+
|
|
72
|
+
## Roles in an invited account
|
|
73
|
+
|
|
74
|
+
Your role in the invited account determines what you can access. The account
|
|
75
|
+
admin assigns your role at invitation and can update it later. For the full
|
|
76
|
+
permission matrix, see [Role Permissions](./roles-and-permissions.mdx).
|
|
77
|
+
|
|
78
|
+
:::note
|
|
79
|
+
|
|
80
|
+
Role-Based Access Control (RBAC) with Developer and Member roles is an
|
|
81
|
+
enterprise feature. On non-enterprise accounts, all invited users are added as
|
|
82
|
+
**Admin** by default.
|
|
83
|
+
|
|
84
|
+
:::
|
|
85
|
+
|
|
86
|
+
## Troubleshooting
|
|
87
|
+
|
|
88
|
+
### I accepted the invitation but I don't see the account
|
|
89
|
+
|
|
90
|
+
This is almost always an **identity mismatch**. Zuplo supports signing in with
|
|
91
|
+
Google, GitHub, and email-password. Different sign-in methods are treated as
|
|
92
|
+
separate identities, even when the underlying email address is the same.
|
|
93
|
+
|
|
94
|
+
Work through this checklist:
|
|
95
|
+
|
|
96
|
+
- **Check the email address on the invitation.** Confirm it matches exactly the
|
|
97
|
+
email you use to sign in to Zuplo.
|
|
98
|
+
- **Check your sign-in method.** If the invitation was sent to `you@company.com`
|
|
99
|
+
and you usually sign in with Google OAuth using that same email, that
|
|
100
|
+
combination should work. But if you also created a separate email-password
|
|
101
|
+
account with the same address, Zuplo treats those as different identities.
|
|
102
|
+
Sign in with the method that matches how the invitation was sent.
|
|
103
|
+
- **Click the invitation link again.** Open the original invitation email and
|
|
104
|
+
click the accept link while signed in with the correct identity. If you were
|
|
105
|
+
signed in with a different identity the first time, the invitation may have
|
|
106
|
+
been applied to the wrong account.
|
|
107
|
+
- **Ask the account admin to verify.** The admin can check **Account Settings →
|
|
108
|
+
Members** to confirm the invitation status. If it shows as pending, it hasn't
|
|
109
|
+
been accepted yet. The admin can also remove and re-send the invitation to
|
|
110
|
+
ensure it goes to the right email.
|
|
111
|
+
|
|
112
|
+
### I see the account but not its projects
|
|
113
|
+
|
|
114
|
+
If you switched into an account but the project list appears empty or you can't
|
|
115
|
+
open a specific project, check the following:
|
|
116
|
+
|
|
117
|
+
- **Your account-level role may not include project visibility.** Users with the
|
|
118
|
+
**Member** role at the account level cannot view projects unless an admin has
|
|
119
|
+
granted them a project-level role. Ask the account admin to assign you a role
|
|
120
|
+
on the specific project you need access to. See
|
|
121
|
+
[Managing Project Members](./managing-project-members.mdx) for details.
|
|
122
|
+
- **The project may require source control access.** Some projects are connected
|
|
123
|
+
to a GitHub, GitLab, Bitbucket, or Azure DevOps repository. You may need
|
|
124
|
+
access to the linked repository in addition to your Zuplo project role.
|
|
125
|
+
|
|
126
|
+
### Why doesn't my invitation link work?
|
|
127
|
+
|
|
128
|
+
Invitation links can fail for a few reasons:
|
|
129
|
+
|
|
130
|
+
- **The link is expired or already used.** Ask the account admin to resend the
|
|
131
|
+
invitation from **Account Settings → Members**.
|
|
132
|
+
- **The link was opened while signed in as the wrong identity.** Sign out, open
|
|
133
|
+
the invitation in a fresh browser session, and sign in with the address the
|
|
134
|
+
invitation was sent to.
|
|
135
|
+
|
|
136
|
+
### Why don't I see the account switcher in my avatar menu?
|
|
137
|
+
|
|
138
|
+
The **Switch Account** submenu only lists other accounts when you belong to more
|
|
139
|
+
than one. If hovering **Switch Account** shows only your current account, you
|
|
140
|
+
belong to a single account. Ask the relevant account admin to send (or resend)
|
|
141
|
+
an invitation to your address.
|
|
142
|
+
|
|
143
|
+
### Do I get the invited account's plan features?
|
|
144
|
+
|
|
145
|
+
Yes. Plan-tier features (Free, Builder, Enterprise) are attached to the
|
|
146
|
+
**account**, not to individual users. While working inside a higher-tier
|
|
147
|
+
account, you have access to all features your role permits on that plan,
|
|
148
|
+
regardless of your personal account's plan.
|
|
149
|
+
|
|
150
|
+
When you switch back to your personal account, you have access only to the
|
|
151
|
+
features available on your personal account's plan.
|
|
152
|
+
|
|
153
|
+
## Related topics
|
|
154
|
+
|
|
155
|
+
- [Managing Account Members](./managing-account-members.mdx): How to invite
|
|
156
|
+
users and manage their roles (admin perspective).
|
|
157
|
+
- [Managing Project Members](./managing-project-members.mdx): How to grant
|
|
158
|
+
project-level access to account members.
|
|
159
|
+
- [Role Permissions](./roles-and-permissions.mdx): Full permission matrix for
|
|
160
|
+
account and project roles.
|
|
@@ -61,136 +61,6 @@ curl \
|
|
|
61
61
|
--header "Authorization: Bearer $ZAPI_KEY"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
## Bucket monetization configuration
|
|
65
|
-
|
|
66
|
-
Each bucket has an optional `MonetizationConfiguration` record that holds
|
|
67
|
-
bucket-wide defaults. The configuration is read by the runtime and the Developer
|
|
68
|
-
Portal.
|
|
69
|
-
|
|
70
|
-
| Method | Path |
|
|
71
|
-
| -------- | ---------------------------------------------------- |
|
|
72
|
-
| `GET` | `/v3/metering/{bucketId}/monetization-configuration` |
|
|
73
|
-
| `PUT` | `/v3/metering/{bucketId}/monetization-configuration` |
|
|
74
|
-
| `DELETE` | `/v3/metering/{bucketId}/monetization-configuration` |
|
|
75
|
-
|
|
76
|
-
The `PUT` endpoint upserts the record. At least one of the four fields below
|
|
77
|
-
must be present. Pass any combination — fields that are omitted retain their
|
|
78
|
-
previous value.
|
|
79
|
-
|
|
80
|
-
| Field | Type | Description |
|
|
81
|
-
| ------------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
-
| `multipleSubscriptionsEnabled` | `boolean` | Stored on the bucket. Reserved for future enforcement of multi-subscription rules; today the Developer Portal create-subscription path enforces a single active subscription per customer regardless of this flag. |
|
|
83
|
-
| `planOrder` | `string[]` | Plan keys in display order. Drives the pricing page sort and is used by [plan changes](./subscription-lifecycle.md#plan-changes-upgrades-and-downgrades) to decide upgrade vs downgrade — moving to a plan with a higher (or equal) index is treated as an upgrade with `"immediate"` timing; a lower index is a downgrade with `"next_billing_cycle"` timing. |
|
|
84
|
-
| `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. Omitted means no filtering; `[]` hides all phases for that plan. |
|
|
85
|
-
| `maxPaymentOverdueDays` | `integer` (`>= 0`) | Bucket-level grace period for overdue payments. Used as the lowest-priority value in the resolution chain: customer metadata → plan metadata → this bucket value → built-in default of `3` days. See [Subscription and payment validation](./monetization-policy.md#subscription-and-payment-validation). |
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
curl -X PUT "https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration" \
|
|
89
|
-
--header "Authorization: Bearer $ZAPI_KEY" \
|
|
90
|
-
--header "Content-Type: application/json" \
|
|
91
|
-
--data '{
|
|
92
|
-
"planOrder": ["free", "developer", "pro"],
|
|
93
|
-
"planSettings": {
|
|
94
|
-
"pro": { "visiblePhases": ["default"] }
|
|
95
|
-
},
|
|
96
|
-
"maxPaymentOverdueDays": 7
|
|
97
|
-
}'
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
`DELETE` removes the record entirely; `GET` on a bucket with no record returns
|
|
101
|
-
the schema defaults (`multipleSubscriptionsEnabled: false`, `planOrder: []`,
|
|
102
|
-
`planSettings: {}`, `maxPaymentOverdueDays: 3`).
|
|
103
|
-
|
|
104
|
-
## Stripe setup and billing readiness
|
|
105
|
-
|
|
106
|
-
These endpoints script the Stripe integration that the
|
|
107
|
-
[Monetization Service UI](./stripe-integration.md#connecting-your-stripe-account)
|
|
108
|
-
runs interactively. They live on the Zuplo developer API, so the request shape
|
|
109
|
-
is documented here.
|
|
110
|
-
|
|
111
|
-
### Connect a Stripe app
|
|
112
|
-
|
|
113
|
-
```http
|
|
114
|
-
POST /v3/metering/{bucketId}/setup/stripe
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Installs a Stripe app on the bucket and creates the default billing profile
|
|
118
|
-
linked to that app.
|
|
119
|
-
|
|
120
|
-
| Field | Type | Description |
|
|
121
|
-
| ------------- | --------- | ------------------------------------------------------------------------------------------------ |
|
|
122
|
-
| `apiKey` | `string` | Stripe secret or restricted key. Required. |
|
|
123
|
-
| `name` | `string` | Display name for the app. Required. |
|
|
124
|
-
| `taxEnabled` | `boolean` | Initial value for `workflow.tax.enabled` on the billing profile. Optional; defaults to `false`. |
|
|
125
|
-
| `taxEnforced` | `boolean` | Initial value for `workflow.tax.enforced` on the billing profile. Optional; defaults to `false`. |
|
|
126
|
-
| `country` | `string` | ISO 3166-1 alpha-2 supplier country for the billing profile. Optional; defaults to `"US"`. |
|
|
127
|
-
|
|
128
|
-
The request fails if the key prefix does not match the bucket environment:
|
|
129
|
-
|
|
130
|
-
- Working-copy or preview buckets accept `sk_test_*` or `rk_test_*`.
|
|
131
|
-
- Production buckets accept `sk_live_*` or `rk_live_*`.
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
curl -X POST "https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe" \
|
|
135
|
-
--header "Authorization: Bearer $ZAPI_KEY" \
|
|
136
|
-
--header "Content-Type: application/json" \
|
|
137
|
-
--data '{
|
|
138
|
-
"apiKey": "rk_test_...",
|
|
139
|
-
"name": "Zuplo Monetization (test)",
|
|
140
|
-
"taxEnabled": false,
|
|
141
|
-
"country": "US"
|
|
142
|
-
}'
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Read the connected Stripe app
|
|
146
|
-
|
|
147
|
-
```http
|
|
148
|
-
GET /v3/metering/{bucketId}/setup/stripe
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Returns a summary of the connected Stripe app, the matched billing profile, and
|
|
152
|
-
connection-test status. Use this to confirm the integration is wired up before
|
|
153
|
-
continuing.
|
|
154
|
-
|
|
155
|
-
### Add a billing profile to a Stripe app
|
|
156
|
-
|
|
157
|
-
```http
|
|
158
|
-
POST /v3/metering/{bucketId}/setup/stripe/{stripeAppId}/billing-profile
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Creates an additional billing profile against an already-installed Stripe app.
|
|
162
|
-
This is rarely needed — the default profile is created during initial setup. Use
|
|
163
|
-
this endpoint to create per-supplier-country profiles.
|
|
164
|
-
|
|
165
|
-
### Check billing readiness
|
|
166
|
-
|
|
167
|
-
```http
|
|
168
|
-
GET /v3/metering/{bucketId}/billing-readiness
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
|
|
173
|
-
```json
|
|
174
|
-
{
|
|
175
|
-
"hasStripeApp": true,
|
|
176
|
-
"stripeAppId": "app_01H...",
|
|
177
|
-
"hasDefaultBillingProfile": true,
|
|
178
|
-
"defaultBillingProfileId": "bp_01H..."
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Use this in setup wizards to gate the UI on whether Stripe is connected.
|
|
183
|
-
|
|
184
|
-
### Update an app
|
|
185
|
-
|
|
186
|
-
```http
|
|
187
|
-
PUT /v3/metering/{bucketId}/apps/{appId}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Replaces an app's configuration (name, description, metadata, and — for Stripe
|
|
191
|
-
apps — `secretAPIKey`). The same key-prefix validation as `POST /setup/stripe`
|
|
192
|
-
applies: a Stripe key must match the bucket environment.
|
|
193
|
-
|
|
194
64
|
## API Reference
|
|
195
65
|
|
|
196
66
|
For complete API operations, see the API Reference documentation:
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Going to Production with Monetization
|
|
3
|
+
sidebar_label: Going to Production
|
|
4
|
+
description:
|
|
5
|
+
Pre-production checklist, Stripe live-mode cutover, billing model readiness,
|
|
6
|
+
and beta limitations for launching Zuplo API monetization.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
:::note{title="Beta"}
|
|
10
|
+
|
|
11
|
+
API Monetization is in beta and free to try. The APIs are stable but should be
|
|
12
|
+
evaluated in non-production environments first. To go to production, contact
|
|
13
|
+
[sales@zuplo.com](mailto:sales@zuplo.com). Production pricing has not yet been
|
|
14
|
+
announced.
|
|
15
|
+
|
|
16
|
+
:::
|
|
17
|
+
|
|
18
|
+
You have built out your monetization configuration in Stripe test mode and your
|
|
19
|
+
customers are ready to pay real money. This guide covers:
|
|
20
|
+
|
|
21
|
+
- The **pre-production checklist**: items to verify before enabling real charges
|
|
22
|
+
- **Billing model readiness**: which pricing models are production-ready today
|
|
23
|
+
- **Stripe live-mode cutover**: step-by-step instructions to connect live
|
|
24
|
+
payments
|
|
25
|
+
- **Beta limitations**: constraints to design around before launch
|
|
26
|
+
|
|
27
|
+
## Before you start
|
|
28
|
+
|
|
29
|
+
Going to production with monetization requires coordination with the Zuplo team.
|
|
30
|
+
The monetization feature is currently in **public beta**. The APIs are stable
|
|
31
|
+
and the core billing flows work end-to-end, but production pricing for the
|
|
32
|
+
monetization feature itself has not yet been announced.
|
|
33
|
+
|
|
34
|
+
:::tip{title="Email sales to go live"}
|
|
35
|
+
|
|
36
|
+
Email [sales@zuplo.com](mailto:sales@zuplo.com) with:
|
|
37
|
+
|
|
38
|
+
- Your account slug and project slug
|
|
39
|
+
- The Stripe account ID you plan to use in production
|
|
40
|
+
- Your target go-live date
|
|
41
|
+
- A summary of your pricing model (flat-fee, overages, pay-as-you-go, etc.)
|
|
42
|
+
|
|
43
|
+
:::
|
|
44
|
+
|
|
45
|
+
The Zuplo team will confirm your configuration, walk you through any
|
|
46
|
+
beta-specific considerations for your use case, and enable your production
|
|
47
|
+
bucket for live billing.
|
|
48
|
+
|
|
49
|
+
## Pre-production checklist
|
|
50
|
+
|
|
51
|
+
Work through each item before enabling real charges. The summary table lists
|
|
52
|
+
every check; the sections below give the detail and links.
|
|
53
|
+
|
|
54
|
+
| # | Check | Why it matters |
|
|
55
|
+
| --- | ---------------------------------- | --------------------------------------------------------------------- |
|
|
56
|
+
| 1 | Authentication provider verified | Customers cannot sign in, subscribe, or manage keys without it |
|
|
57
|
+
| 2 | Meters, features, plans configured | Configuration is per-bucket and does not promote between environments |
|
|
58
|
+
| 3 | Stripe live-mode connected | Test keys and live keys are completely separate environments |
|
|
59
|
+
| 4 | Billing profile configured | Tax calculations and supplier country depend on it |
|
|
60
|
+
| 5 | Webhook endpoint validated | Payment events (charges, failures, disputes) must reach Zuplo |
|
|
61
|
+
| 6 | Quota behavior chosen and tested | Hard vs. soft limits change customer experience and billing |
|
|
62
|
+
| 7 | Subscription lifecycle tested | Every state transition must work before real money is on the line |
|
|
63
|
+
| 8 | Payment grace period configured | Controls when overdue customers lose API access |
|
|
64
|
+
|
|
65
|
+
### Authentication provider verified
|
|
66
|
+
|
|
67
|
+
Your Developer Portal must have an authentication provider configured so that
|
|
68
|
+
customers can sign in, subscribe, and manage their API keys. Verify that your
|
|
69
|
+
auth provider (Auth0, Clerk, or a custom OpenID Connect provider) works
|
|
70
|
+
correctly across all environments: working copy, preview, and production.
|
|
71
|
+
|
|
72
|
+
See
|
|
73
|
+
[Developer Portal Setup → Prerequisites](./developer-portal.md#prerequisites)
|
|
74
|
+
for details.
|
|
75
|
+
|
|
76
|
+
### Meters, features, and plans configured
|
|
77
|
+
|
|
78
|
+
Confirm that your production bucket has the same meters, features, and plans you
|
|
79
|
+
tested in your working-copy or preview bucket. Monetization configuration is
|
|
80
|
+
scoped per-bucket, so you must recreate it in production. It does not
|
|
81
|
+
automatically promote between environments.
|
|
82
|
+
|
|
83
|
+
Key checks:
|
|
84
|
+
|
|
85
|
+
- **Meter keys match policy configuration.** The meter `slug`/`eventType`, the
|
|
86
|
+
feature `key`, and the `meters` map in your `MonetizationInboundPolicy` must
|
|
87
|
+
all use the same key. See
|
|
88
|
+
[Meters → Naming Consistency](./meters.mdx#naming-consistency).
|
|
89
|
+
- **Plans are published.** Draft plans are not visible to customers. Publish
|
|
90
|
+
each plan from the Monetization Service in the Zuplo Portal.
|
|
91
|
+
- **Currency is correct.** Plans support any ISO 4217 currency code (USD, EUR,
|
|
92
|
+
AUD, GBP, etc.). Verify the `currency` field on every plan. It cannot be
|
|
93
|
+
changed after a plan is created.
|
|
94
|
+
- **Plan ordering is set.** The
|
|
95
|
+
[monetization configuration](./api-access.mdx#bucket-monetization-configuration)
|
|
96
|
+
`planOrder` array controls both the pricing page display order and
|
|
97
|
+
upgrade/downgrade logic. Make sure it reflects your intended tier hierarchy.
|
|
98
|
+
|
|
99
|
+
### Stripe live-mode connected
|
|
100
|
+
|
|
101
|
+
Your production environment must use a Stripe **live** key (`sk_live_*` or
|
|
102
|
+
`rk_live_*`). Test keys and live keys are completely separate environments in
|
|
103
|
+
Stripe. Customers, invoices, and payment methods created in test mode do not
|
|
104
|
+
exist in live mode.
|
|
105
|
+
|
|
106
|
+
:::tip
|
|
107
|
+
|
|
108
|
+
Use a **restricted key** (`rk_live_*`) rather than a secret key. A restricted
|
|
109
|
+
key follows the principle of least privilege. See
|
|
110
|
+
[Using a Stripe restricted key](./stripe-integration.md#using-a-stripe-restricted-key)
|
|
111
|
+
for the exact eight permissions your key needs.
|
|
112
|
+
|
|
113
|
+
:::
|
|
114
|
+
|
|
115
|
+
:::caution
|
|
116
|
+
|
|
117
|
+
Use one Stripe key type per Zuplo environment. Do not replace a test key with a
|
|
118
|
+
live key in the same environment. Zuplo rejects a live key on a non-production
|
|
119
|
+
bucket and a test key on a production bucket.
|
|
120
|
+
|
|
121
|
+
:::
|
|
122
|
+
|
|
123
|
+
### Billing profile configured
|
|
124
|
+
|
|
125
|
+
Every bucket that processes payments needs a default billing profile. The
|
|
126
|
+
billing profile is created automatically when you connect Stripe, but you should
|
|
127
|
+
verify:
|
|
128
|
+
|
|
129
|
+
| Setting | What to check | Reference |
|
|
130
|
+
| -------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
131
|
+
| **Supplier country** | Set correctly. Tax calculations depend on this value | [Enable tax collection](./tax-collection.md#enable-tax-collection) |
|
|
132
|
+
| **Tax collection** | Tax registrations added in Stripe and `workflow.tax.enabled` set | [Add tax registrations in Stripe](./tax-collection.md#add-tax-registrations-in-stripe) |
|
|
133
|
+
| **Tax behavior** | Inclusive vs. exclusive matches your pricing page | [Tax behavior configuration](./tax-collection.md#tax-behavior-configuration) |
|
|
134
|
+
|
|
135
|
+
### Webhook endpoint validated
|
|
136
|
+
|
|
137
|
+
When you connect Stripe, Zuplo registers a webhook endpoint automatically so it
|
|
138
|
+
can react to payment events (successful charges, failed payments, disputes).
|
|
139
|
+
Verify the webhook is healthy:
|
|
140
|
+
|
|
141
|
+
<Stepper>
|
|
142
|
+
|
|
143
|
+
1. Open your
|
|
144
|
+
[Stripe Dashboard → Developers → Webhooks](https://dashboard.stripe.com/webhooks).
|
|
145
|
+
|
|
146
|
+
1. Find the Zuplo-managed endpoint.
|
|
147
|
+
|
|
148
|
+
1. Confirm recent deliveries show `2xx` responses (typically `201 OK`), not
|
|
149
|
+
failures or timeouts.
|
|
150
|
+
|
|
151
|
+
</Stepper>
|
|
152
|
+
|
|
153
|
+
<ModalScreenshot size="md">
|
|
154
|
+
|
|
155
|
+

|
|
156
|
+
|
|
157
|
+
</ModalScreenshot>
|
|
158
|
+
|
|
159
|
+
### Quota behavior chosen and tested
|
|
160
|
+
|
|
161
|
+
Each metered entitlement can enforce a **hard limit** or a **soft limit**:
|
|
162
|
+
|
|
163
|
+
| Setting | Quota exhausted behavior | Overage billed? |
|
|
164
|
+
| -------------------- | -------------------------------- | ------------------------ |
|
|
165
|
+
| `isSoftLimit: false` | Returns `403 Forbidden` | No |
|
|
166
|
+
| `isSoftLimit: true` | Request allowed, usage continues | Yes, per-unit in arrears |
|
|
167
|
+
|
|
168
|
+
Test both paths in your working-copy environment before going live:
|
|
169
|
+
|
|
170
|
+
<Stepper>
|
|
171
|
+
|
|
172
|
+
1. Subscribe to a plan with a low quota (e.g., 10 requests).
|
|
173
|
+
|
|
174
|
+
1. Exceed the quota and verify the correct behavior: either a `403` response
|
|
175
|
+
(hard limit) or continued access with usage counting above the entitlement
|
|
176
|
+
(soft limit).
|
|
177
|
+
|
|
178
|
+
1. Check the Stripe test dashboard to verify that invoices include the expected
|
|
179
|
+
line items.
|
|
180
|
+
|
|
181
|
+
</Stepper>
|
|
182
|
+
|
|
183
|
+
See [Rate Cards](./rate-cards.mdx) for how `isSoftLimit` and `issueAfterReset`
|
|
184
|
+
interact, and [Monetization Policy Reference](./monetization-policy.md) for how
|
|
185
|
+
the gateway enforces limits.
|
|
186
|
+
|
|
187
|
+
### Subscription lifecycle tested
|
|
188
|
+
|
|
189
|
+
Before real money is on the line, walk through every lifecycle state:
|
|
190
|
+
|
|
191
|
+
| State | How to test |
|
|
192
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
193
|
+
| Subscribe | Subscribe to a plan via the Developer Portal using [Stripe test cards](https://docs.stripe.com/testing) |
|
|
194
|
+
| Upgrade | Move from a lower plan to a higher plan. Entitlements should change immediately and the proration credit should appear on the next invoice |
|
|
195
|
+
| Downgrade | Move from a higher plan to a lower plan. The change should take effect at the next billing cycle, not immediately |
|
|
196
|
+
| Cancel | Cancel a subscription. Access should continue until the billing period ends, then be revoked |
|
|
197
|
+
| Reactivate | Reactivate a canceled subscription before the period ends. Access should be restored |
|
|
198
|
+
| Failed payment | Use Stripe test card `4000 0000 0000 0341` (attaches to customer but fails on charge) to verify grace period behavior and that access is blocked after the configured `maxPaymentOverdueDays` |
|
|
199
|
+
|
|
200
|
+
See [Subscription Lifecycle](./subscription-lifecycle.md) for the full details
|
|
201
|
+
on each state transition.
|
|
202
|
+
|
|
203
|
+
### Payment grace period configured
|
|
204
|
+
|
|
205
|
+
The default grace period for overdue payments is 3 days. During this window,
|
|
206
|
+
customers retain API access while Stripe retries the charge. After the grace
|
|
207
|
+
period, access is blocked.
|
|
208
|
+
|
|
209
|
+
You can customize the grace period at three levels. Higher precedence overrides
|
|
210
|
+
lower:
|
|
211
|
+
|
|
212
|
+
| Precedence | Where | Field / Key |
|
|
213
|
+
| ----------- | -------------------------- | ----------------------------------------------------------------------------------------- |
|
|
214
|
+
| 1 (highest) | Stripe customer metadata | `zuplo_max_payment_overdue_days` |
|
|
215
|
+
| 2 | Stripe plan metadata | `zuplo_max_payment_overdue_days` |
|
|
216
|
+
| 3 (lowest) | Bucket monetization config | `maxPaymentOverdueDays` ([reference](./api-access.mdx#bucket-monetization-configuration)) |
|
|
217
|
+
|
|
218
|
+
Set the value to `0` to block access immediately when payment fails.
|
|
219
|
+
|
|
220
|
+
## Billing models in production
|
|
221
|
+
|
|
222
|
+
Not all billing models have the same level of portal support today. Choose the
|
|
223
|
+
right model for your launch based on current readiness.
|
|
224
|
+
|
|
225
|
+
| Model | Status | Notes |
|
|
226
|
+
| ---------------------------- | ---------------------- | ------------------------------------------------------------- |
|
|
227
|
+
| Fixed monthly quotas | Production-ready | Fully supported end-to-end |
|
|
228
|
+
| Monthly quotas with overages | Production-ready | Modeled as graduated tiered pricing with `isSoftLimit: true` |
|
|
229
|
+
| Pay-as-you-go | Limited portal support | Underlying model works; portal pricing table not fully tested |
|
|
230
|
+
| Credits / tokens (prepaid) | Limited portal support | Underlying model works; portal experience not fully tested |
|
|
231
|
+
|
|
232
|
+
### Fixed monthly quotas (production-ready)
|
|
233
|
+
|
|
234
|
+
The most common and most stable model. Customers pay a flat monthly price for an
|
|
235
|
+
included number of requests. When the quota is exhausted, the API returns
|
|
236
|
+
`403 Forbidden` (hard limit) or bills overages (soft limit).
|
|
237
|
+
|
|
238
|
+
This model is fully supported in the Developer Portal pricing table, checkout
|
|
239
|
+
flow, usage dashboard, and invoicing.
|
|
240
|
+
|
|
241
|
+
See
|
|
242
|
+
[Billing Models → Fixed monthly quotas](./billing-models.md#fixed-monthly-quotas)
|
|
243
|
+
for configuration examples.
|
|
244
|
+
|
|
245
|
+
### Monthly quotas with overages (production-ready)
|
|
246
|
+
|
|
247
|
+
A hybrid model where customers pay a base price for an included allowance, with
|
|
248
|
+
per-unit overage billing in arrears for usage above the limit. This is modeled
|
|
249
|
+
using graduated tiered pricing with `isSoftLimit: true`.
|
|
250
|
+
|
|
251
|
+
Fully supported end-to-end. See
|
|
252
|
+
[Billing Models → Monthly quotas with overages](./billing-models.md#monthly-quotas-with-overages)
|
|
253
|
+
for examples.
|
|
254
|
+
|
|
255
|
+
### Pay-as-you-go (limited portal support)
|
|
256
|
+
|
|
257
|
+
Pure pay-as-you-go billing (no upfront cost, bill entirely in arrears for actual
|
|
258
|
+
usage) is supported by the underlying monetization models, but the **Developer
|
|
259
|
+
Portal pricing table experience has not been fully tested** for this billing
|
|
260
|
+
model yet.
|
|
261
|
+
|
|
262
|
+
If you need pay-as-you-go pricing in production, contact
|
|
263
|
+
[support@zuplo.com](mailto:support@zuplo.com) to discuss your use case and
|
|
264
|
+
current workarounds.
|
|
265
|
+
|
|
266
|
+
See [Billing Models → Pay-as-you-go](./billing-models.md#pay-as-you-go) for the
|
|
267
|
+
data model and configuration.
|
|
268
|
+
|
|
269
|
+
### Credits / tokens (prepaid, limited portal support)
|
|
270
|
+
|
|
271
|
+
Credit/token-based billing is supported by the underlying data model, but the
|
|
272
|
+
**Developer Portal experience has not been fully tested** for this billing
|
|
273
|
+
model.
|
|
274
|
+
|
|
275
|
+
If you need prepaid credit billing, contact
|
|
276
|
+
[sales@zuplo.com](mailto:sales@zuplo.com) to discuss your use case.
|
|
277
|
+
|
|
278
|
+
See
|
|
279
|
+
[Billing Models → Credits / tokens](./billing-models.md#credits--tokens-prepaid)
|
|
280
|
+
for configuration examples.
|
|
281
|
+
|
|
282
|
+
## How usage metering works with Stripe
|
|
283
|
+
|
|
284
|
+
A common point of confusion: Zuplo does **not** use Stripe Billing Meters,
|
|
285
|
+
Stripe Subscriptions, Stripe Products, or Stripe Prices. Zuplo manages plans,
|
|
286
|
+
subscriptions, metering, and entitlements internally. Stripe is used only for
|
|
287
|
+
**money**: collecting payments and issuing invoices.
|
|
288
|
+
|
|
289
|
+

|
|
290
|
+
|
|
291
|
+
The flow works like this:
|
|
292
|
+
|
|
293
|
+
1. Every API request that hits a monetized route is metered in real time by the
|
|
294
|
+
`MonetizationInboundPolicy`.
|
|
295
|
+
2. Usage events are aggregated internally by Zuplo. They are not sent to Stripe
|
|
296
|
+
as individual events.
|
|
297
|
+
3. At the end of each billing period, Zuplo calculates the total usage, applies
|
|
298
|
+
the plan's pricing model (flat fee, tiered, per-unit, etc.), and creates a
|
|
299
|
+
**Stripe Invoice** with the appropriate line items.
|
|
300
|
+
4. Stripe collects payment from the customer's saved payment method.
|
|
301
|
+
|
|
302
|
+
This means you will **not** see usage events, billing meters, or subscription
|
|
303
|
+
objects in your Stripe dashboard. You will see **Customers** and **Invoices**.
|
|
304
|
+
To check usage and subscription state, use the
|
|
305
|
+
[Zuplo metering API](./api-access.mdx#authentication) or the Developer Portal's
|
|
306
|
+
usage dashboard.
|
|
307
|
+
|
|
308
|
+
<details>
|
|
309
|
+
<summary>Why usage events might not appear in Stripe</summary>
|
|
310
|
+
|
|
311
|
+
If you expected to see per-request usage events in Stripe and they are not
|
|
312
|
+
there, this is by design. Zuplo does not call Stripe's metered billing APIs.
|
|
313
|
+
Usage is materialized as invoice line items at billing time only.
|
|
314
|
+
|
|
315
|
+
To verify that metering is working:
|
|
316
|
+
|
|
317
|
+
1. Make API requests to a monetized endpoint.
|
|
318
|
+
2. Check the Developer Portal usage dashboard. It should show real-time usage.
|
|
319
|
+
3. Query the meter directly via the API:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
curl -X POST "https://dev.zuplo.com/v3/metering/$BUCKET_ID/meters/api_requests/query" \
|
|
323
|
+
-H "Authorization: Bearer $ZAPI_KEY" \
|
|
324
|
+
-H "Content-Type: application/json" \
|
|
325
|
+
-d '{
|
|
326
|
+
"filterSubscription": ["SUBSCRIPTION_ID"],
|
|
327
|
+
"from": "2026-05-01T00:00:00Z",
|
|
328
|
+
"to": "2026-05-31T23:59:59Z",
|
|
329
|
+
"windowSize": "DAY"
|
|
330
|
+
}'
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
If the usage dashboard shows zero despite active traffic, see
|
|
334
|
+
[Troubleshooting → Usage dashboard shows zero](./troubleshooting.md#usage-dashboard-shows-zero-despite-active-api-traffic).
|
|
335
|
+
|
|
336
|
+
</details>
|
|
337
|
+
|
|
338
|
+
## Connecting Stripe live mode
|
|
339
|
+
|
|
340
|
+
When your test configuration is validated and you are ready to accept real
|
|
341
|
+
payments, follow these steps to connect your production environment to Stripe
|
|
342
|
+
live mode.
|
|
343
|
+
|
|
344
|
+
### Step 1: Create a Stripe restricted key for production
|
|
345
|
+
|
|
346
|
+
<Stepper>
|
|
347
|
+
|
|
348
|
+
1. In the [Stripe Dashboard](https://dashboard.stripe.com), switch to **live
|
|
349
|
+
mode** (toggle in the top-right corner).
|
|
350
|
+
|
|
351
|
+
1. Go to **Developers → API keys → Create restricted key**.
|
|
352
|
+
|
|
353
|
+
1. Name the key `Zuplo Monetization (production)`.
|
|
354
|
+
|
|
355
|
+
1. Enable the eight permissions listed in
|
|
356
|
+
[Using a Stripe restricted key](./stripe-integration.md#using-a-stripe-restricted-key).
|
|
357
|
+
|
|
358
|
+
1. Click **Create key** and copy the value (`rk_live_...`).
|
|
359
|
+
|
|
360
|
+
</Stepper>
|
|
361
|
+
|
|
362
|
+
### Step 2: Connect to your production environment
|
|
363
|
+
|
|
364
|
+
<Stepper>
|
|
365
|
+
|
|
366
|
+
1. Open your project's
|
|
367
|
+
[**Services**](https://portal.zuplo.com/+/account/project/services) page in
|
|
368
|
+
the Zuplo Portal.
|
|
369
|
+
|
|
370
|
+
1. Select the **Production** environment.
|
|
371
|
+
|
|
372
|
+
1. Open the Monetization Service, then go to **Payment Provider**.
|
|
373
|
+
|
|
374
|
+
1. Paste your live restricted key (`rk_live_...`) and click **Save**.
|
|
375
|
+
|
|
376
|
+
</Stepper>
|
|
377
|
+
|
|
378
|
+
<ModalScreenshot size="md">
|
|
379
|
+
|
|
380
|
+

|
|
381
|
+
|
|
382
|
+
</ModalScreenshot>
|
|
383
|
+
|
|
384
|
+
### Step 3: Recreate your monetization configuration
|
|
385
|
+
|
|
386
|
+
Because monetization configuration is scoped per-bucket, you need to set up
|
|
387
|
+
meters, features, plans, and the monetization configuration in your production
|
|
388
|
+
bucket. You can do this through the Portal UI or via the
|
|
389
|
+
[monetization APIs](./api-access.mdx).
|
|
390
|
+
|
|
391
|
+
### Step 4: Verify with a real charge
|
|
392
|
+
|
|
393
|
+
<Stepper>
|
|
394
|
+
|
|
395
|
+
1. Publish at least one plan in your production environment.
|
|
396
|
+
|
|
397
|
+
1. Open the Developer Portal on your production URL.
|
|
398
|
+
|
|
399
|
+
1. Subscribe to a plan using a real payment method.
|
|
400
|
+
|
|
401
|
+
1. Confirm in the Stripe Dashboard (live mode) that a **Customer** was created
|
|
402
|
+
and the **Webhook** endpoint is registered and showing `2xx` responses.
|
|
403
|
+
|
|
404
|
+
1. Make a few API requests and verify the usage dashboard updates.
|
|
405
|
+
|
|
406
|
+
1. Cancel the test subscription when done.
|
|
407
|
+
|
|
408
|
+
</Stepper>
|
|
409
|
+
|
|
410
|
+
### Step 5: Monitor first production transactions
|
|
411
|
+
|
|
412
|
+
After going live, keep a close eye on:
|
|
413
|
+
|
|
414
|
+
| Surface | What to check |
|
|
415
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
416
|
+
| Stripe Dashboard → Invoices | Invoices are created at the end of each billing period with the correct line items |
|
|
417
|
+
| Stripe Dashboard → Webhooks | All webhook deliveries are succeeding |
|
|
418
|
+
| Developer Portal → Usage Dashboard | Customer-facing usage numbers match your expectations |
|
|
419
|
+
| API responses | Monetized routes return `200` for subscribed customers and `403` for over-quota requests |
|
|
420
|
+
|
|
421
|
+
## Known beta limitations
|
|
422
|
+
|
|
423
|
+
The following limitations apply during the beta period. Design around them when
|
|
424
|
+
planning your production launch. As noted in
|
|
425
|
+
[Before you start](#before-you-start), production access requires coordination
|
|
426
|
+
with the Zuplo sales team, and production pricing for the monetization feature
|
|
427
|
+
has not yet been announced.
|
|
428
|
+
|
|
429
|
+
<details>
|
|
430
|
+
<summary>Pay-as-you-go and credits portal experience</summary>
|
|
431
|
+
|
|
432
|
+
Pure pay-as-you-go and credit/token-based billing models are supported by the
|
|
433
|
+
underlying data model and APIs, but the Developer Portal pricing table has not
|
|
434
|
+
been fully tested for these models. If your business requires either model,
|
|
435
|
+
coordinate with Zuplo support for guidance on workarounds.
|
|
436
|
+
|
|
437
|
+
</details>
|
|
438
|
+
|
|
439
|
+
<details>
|
|
440
|
+
<summary>Configuration does not promote between environments</summary>
|
|
441
|
+
|
|
442
|
+
Meters, features, plans, and monetization configuration are scoped to individual
|
|
443
|
+
buckets. There is no built-in promotion mechanism to copy configuration from
|
|
444
|
+
working-copy to production. You must recreate or script the configuration in
|
|
445
|
+
each environment separately using the APIs documented in
|
|
446
|
+
[API Access](./api-access.mdx).
|
|
447
|
+
|
|
448
|
+
</details>
|
|
449
|
+
|
|
450
|
+
<details>
|
|
451
|
+
<summary>Single active subscription per customer (default)</summary>
|
|
452
|
+
|
|
453
|
+
By default, each customer can hold one active subscription at a time.
|
|
454
|
+
Multi-subscription support (e.g., a primary subscription plus a credit pack) is
|
|
455
|
+
available on request. Contact [sales@zuplo.com](mailto:sales@zuplo.com) to
|
|
456
|
+
enable it for your bucket.
|
|
457
|
+
|
|
458
|
+
See
|
|
459
|
+
[Subscription Lifecycle → Subscriptions per customer](./subscription-lifecycle.md#subscriptions-per-customer)
|
|
460
|
+
for details on multi-subscription scenarios.
|
|
461
|
+
|
|
462
|
+
</details>
|
|
463
|
+
|
|
464
|
+
## Zuplo plan requirements for monetization
|
|
465
|
+
|
|
466
|
+
Monetization is available during beta on all Zuplo plan tiers, including Free
|
|
467
|
+
and Builder. However, production workloads typically need capabilities that
|
|
468
|
+
exceed what the Free and Builder tiers offer.
|
|
469
|
+
|
|
470
|
+
| Plan | Gateway devs | Custom domains | Support | Production fit |
|
|
471
|
+
| ----------------------- | ------------ | -------------- | --------------------------- | ----------------------------------------------------------------- |
|
|
472
|
+
| **Free** | Up to 2 | 0 | Community | Testing monetization only; not suitable for production billing |
|
|
473
|
+
| **Builder** ($25/mo) | Up to 2 | 2 | Community | Small-scale production if you don't need more team members or SLA |
|
|
474
|
+
| **Enterprise** (custom) | Custom | Custom | Priority, SLA up to 99.999% | Required for >2 devs, additional domains, log retention, or SLA |
|
|
475
|
+
|
|
476
|
+
There is currently no self-serve plan between Builder and Enterprise. If you
|
|
477
|
+
need capabilities beyond Builder but are not ready for a full Enterprise
|
|
478
|
+
engagement, email [sales@zuplo.com](mailto:sales@zuplo.com) to discuss your
|
|
479
|
+
options.
|
|
480
|
+
|
|
481
|
+
For current plan details and pricing, see the
|
|
482
|
+
[Zuplo pricing page](https://zuplo.com/pricing).
|
|
483
|
+
|
|
484
|
+
## Getting help when going live
|
|
485
|
+
|
|
486
|
+
| Situation | Contact |
|
|
487
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
488
|
+
| Ready to enable production billing for the first time, discussing production pricing, multi-subscription support, or a plan between Builder and Enterprise | [sales@zuplo.com](mailto:sales@zuplo.com) |
|
|
489
|
+
| Something is not working as expected, debugging a billing or metering issue, or questions about a beta limitation or workaround | [support@zuplo.com](mailto:support@zuplo.com) |
|
|
490
|
+
|
|
491
|
+
### What to include in your request
|
|
492
|
+
|
|
493
|
+
To help the team resolve your issue quickly, include:
|
|
494
|
+
|
|
495
|
+
| Field | Where to find / what to provide |
|
|
496
|
+
| ------------------ | ------------------------------------------------------ |
|
|
497
|
+
| Account slug | Your Zuplo account identifier |
|
|
498
|
+
| Project slug | The project with monetization enabled |
|
|
499
|
+
| Bucket ID | Project Services → Bucket Details |
|
|
500
|
+
| Stripe account ID | Stripe Dashboard → Settings, starts with `acct_` |
|
|
501
|
+
| Plan keys | The plan keys involved in the issue |
|
|
502
|
+
| Subscription ID | If the issue is subscription-specific |
|
|
503
|
+
| Steps to reproduce | What you did, what you expected, what happened instead |
|
|
504
|
+
|
|
505
|
+
## Next steps
|
|
506
|
+
|
|
507
|
+
Once you have completed the checklist, connected Stripe live mode, and verified
|
|
508
|
+
your first real charge, your monetized API is in production. Monitor your
|
|
509
|
+
[Stripe Dashboard](https://dashboard.stripe.com) webhooks and invoices closely
|
|
510
|
+
during the first billing cycle.
|
|
511
|
+
|
|
512
|
+
- [Troubleshooting](./troubleshooting.md): common issues and debugging tools
|
|
513
|
+
- [Subscription Lifecycle](./subscription-lifecycle.md): managing ongoing
|
|
514
|
+
upgrades, downgrades, and cancellations
|
|
515
|
+
- [Billing Models Guide](./billing-models.md): exploring additional pricing
|
|
516
|
+
strategies as your business grows
|
|
@@ -110,5 +110,6 @@ pricing, wire up Stripe, and configure your gateway to enforce quotas.
|
|
|
110
110
|
| [Subscription Lifecycle](./monetization/subscription-lifecycle.md) | Managing trials, upgrades, downgrades, and cancellations |
|
|
111
111
|
| [Private Plans](./monetization/private-plans.md) | Invite-only plans for specific users |
|
|
112
112
|
| [Tax Collection](./monetization/tax-collection.md) | Enabling VAT, sales tax, or GST via Stripe Tax |
|
|
113
|
+
| [Going to Production](./monetization/going-to-production.mdx) | Pre-launch checklist, Stripe live mode, and beta considerations |
|
|
113
114
|
| [Plan Examples](./monetization/plan-examples.mdx) | Real-world plan configurations |
|
|
114
115
|
| [Troubleshooting](./monetization/troubleshooting.md) | Common issues, debugging, and FAQ |
|
|
@@ -154,10 +154,7 @@ below it:
|
|
|
154
154
|
|
|
155
155
|
1. **Customer metadata** — `zuplo_max_payment_overdue_days` on the customer
|
|
156
156
|
2. **Plan metadata** — `zuplo_max_payment_overdue_days` on the plan
|
|
157
|
-
3. **
|
|
158
|
-
monetization configuration (PUT
|
|
159
|
-
`/v3/metering/{bucketId}/monetization-configuration`)
|
|
160
|
-
4. **Default** — `3` days
|
|
157
|
+
3. **Default** — `3` days
|
|
161
158
|
|
|
162
159
|
Set the value to `0` to block requests immediately when payment is overdue.
|
|
163
160
|
|
|
@@ -190,6 +190,17 @@ curl \
|
|
|
190
190
|
"name": "Default",
|
|
191
191
|
"duration": null,
|
|
192
192
|
"rateCards": [
|
|
193
|
+
{
|
|
194
|
+
"type": "flat_fee",
|
|
195
|
+
"key": "subscription_fee",
|
|
196
|
+
"name": "Starter Plan Subscription",
|
|
197
|
+
"billingCadence": "P1M",
|
|
198
|
+
"price": {
|
|
199
|
+
"type": "flat",
|
|
200
|
+
"amount": "9.99",
|
|
201
|
+
"paymentTerm": "in_advance"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
193
204
|
{
|
|
194
205
|
"type": "usage_based",
|
|
195
206
|
"key": "api_requests",
|
|
@@ -208,7 +219,7 @@ curl \
|
|
|
208
219
|
"tiers": [
|
|
209
220
|
{
|
|
210
221
|
"upToAmount": "1000",
|
|
211
|
-
"flatPrice": { "type": "flat", "amount": "
|
|
222
|
+
"flatPrice": { "type": "flat", "amount": "0.00" },
|
|
212
223
|
"unitPrice": null
|
|
213
224
|
},
|
|
214
225
|
{
|
|
@@ -227,10 +238,16 @@ EOF
|
|
|
227
238
|
|
|
228
239
|
**What changed:**
|
|
229
240
|
|
|
230
|
-
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
241
|
+
- Split the default phase into two rate cards: a billing-only `flat_fee` rate
|
|
242
|
+
card (no `featureKey`, `paymentTerm: "in_advance"`) that collects the $9.99
|
|
243
|
+
subscription fee at the start of each billing period, and a `usage_based` rate
|
|
244
|
+
card that grants the entitlement and prices overage
|
|
245
|
+
- Tier 1's `flatPrice` is set to $0 because the $9.99 is now collected by the
|
|
246
|
+
separate `flat_fee` rate card — leaving it at $9.99 here would double-charge
|
|
247
|
+
the customer in arrears at the end of the period
|
|
248
|
+
- Changed `isSoftLimit` to `true` so requests continue past 1,000 and tier 2
|
|
249
|
+
applies — overage is billed at $0.01 per request above the allowance, in
|
|
250
|
+
arrears at the end of the period
|
|
234
251
|
|
|
235
252
|
**Example billing:**
|
|
236
253
|
|
|
@@ -305,6 +322,17 @@ curl \
|
|
|
305
322
|
"name": "Default",
|
|
306
323
|
"duration": null,
|
|
307
324
|
"rateCards": [
|
|
325
|
+
{
|
|
326
|
+
"type": "flat_fee",
|
|
327
|
+
"key": "subscription_fee",
|
|
328
|
+
"name": "Starter Plan Subscription",
|
|
329
|
+
"billingCadence": "P1M",
|
|
330
|
+
"price": {
|
|
331
|
+
"type": "flat",
|
|
332
|
+
"amount": "9.99",
|
|
333
|
+
"paymentTerm": "in_advance"
|
|
334
|
+
}
|
|
335
|
+
},
|
|
308
336
|
{
|
|
309
337
|
"type": "usage_based",
|
|
310
338
|
"key": "api_requests",
|
|
@@ -323,7 +351,7 @@ curl \
|
|
|
323
351
|
"tiers": [
|
|
324
352
|
{
|
|
325
353
|
"upToAmount": "1000",
|
|
326
|
-
"flatPrice": { "type": "flat", "amount": "
|
|
354
|
+
"flatPrice": { "type": "flat", "amount": "0.00" },
|
|
327
355
|
"unitPrice": null
|
|
328
356
|
},
|
|
329
357
|
{
|
|
@@ -49,15 +49,6 @@ in Stripe at the moment a charge is needed (as invoice line items).
|
|
|
49
49
|
3. Paste your **Stripe API Key**
|
|
50
50
|
4. Click **Save**
|
|
51
51
|
|
|
52
|
-
:::tip
|
|
53
|
-
|
|
54
|
-
To script the same flow (CI, infrastructure-as-code, etc.) use the
|
|
55
|
-
[Stripe setup API](./api-access.mdx#stripe-setup-and-billing-readiness) — it
|
|
56
|
-
exposes `POST /setup/stripe`, `GET /billing-readiness`, and key-rotation
|
|
57
|
-
endpoints with the same prefix validation as the UI.
|
|
58
|
-
|
|
59
|
-
:::
|
|
60
|
-
|
|
61
52
|
The connection authorizes Zuplo to manage Stripe objects on your behalf —
|
|
62
53
|
specifically Customers, Checkout Sessions, Customer Portal Sessions, Invoices,
|
|
63
54
|
and Tax Calculations. See
|
|
@@ -255,9 +246,8 @@ Stripe retries the payment.
|
|
|
255
246
|
| `failed` | Access blocked after grace period (configurable) |
|
|
256
247
|
| `uncollectible` | Access blocked |
|
|
257
248
|
|
|
258
|
-
The grace period is configurable
|
|
259
|
-
metadata
|
|
260
|
-
days. See
|
|
249
|
+
The grace period is configurable via customer or plan metadata, with customer
|
|
250
|
+
metadata overriding plan metadata. Default is 3 days. See
|
|
261
251
|
[Subscription and payment validation](./monetization-policy.md#subscription-and-payment-validation)
|
|
262
252
|
for the full resolution order.
|
|
263
253
|
|
|
@@ -26,13 +26,12 @@ announced.
|
|
|
26
26
|
access is blocked.
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
curl https://dev.zuplo.com/v3/metering
|
|
30
|
-
-H "Authorization: Bearer {API_KEY}"
|
|
29
|
+
curl https://dev.zuplo.com/v3/metering/${BUCKET_ID}/customers/${CUSTOMER_ID}/subscriptions \
|
|
30
|
+
-H "Authorization: Bearer ${API_KEY}"
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Fix: Either resolve the payment issue in Stripe, or adjust the grace period.
|
|
34
|
-
The window resolves customer metadata → plan metadata →
|
|
35
|
-
→ 3-day default. See
|
|
34
|
+
The window resolves customer metadata → plan metadata → 3-day default. See
|
|
36
35
|
[Subscription and payment validation](./monetization-policy.md#subscription-and-payment-validation)
|
|
37
36
|
for details.
|
|
38
37
|
|
|
@@ -142,27 +141,29 @@ entitlements don't change.
|
|
|
142
141
|
|
|
143
142
|
```bash
|
|
144
143
|
# List subscriptions for a customer
|
|
145
|
-
curl https://dev.zuplo.com/v3/metering
|
|
146
|
-
-H "Authorization: Bearer {API_KEY}"
|
|
144
|
+
curl https://dev.zuplo.com/v3/metering/${BUCKET_ID}/customers/${CUSTOMER_ID}/subscriptions \
|
|
145
|
+
-H "Authorization: Bearer ${API_KEY}"
|
|
147
146
|
|
|
148
147
|
# Check subscription access and entitlements
|
|
149
|
-
curl https://dev.zuplo.com/v3/metering
|
|
150
|
-
-H "Authorization: Bearer {API_KEY}"
|
|
148
|
+
curl https://dev.zuplo.com/v3/metering/${BUCKET_ID}/subscriptions/${SUBSCRIPTION_ID}/access \
|
|
149
|
+
-H "Authorization: Bearer ${API_KEY}"
|
|
151
150
|
```
|
|
152
151
|
|
|
153
152
|
### Check meter usage
|
|
154
153
|
|
|
155
154
|
```bash
|
|
156
155
|
# Query meter usage for the current month
|
|
157
|
-
curl -X POST https://dev.zuplo.com/v3/metering
|
|
158
|
-
-H "Authorization: Bearer {API_KEY}" \
|
|
156
|
+
curl -X POST https://dev.zuplo.com/v3/metering/${BUCKET_ID}/meters/${METER_ID_OR_SLUG}/query \
|
|
157
|
+
-H "Authorization: Bearer ${API_KEY}" \
|
|
159
158
|
-H "Content-Type: application/json" \
|
|
160
|
-
-d
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
-d @- <<EOF
|
|
160
|
+
{
|
|
161
|
+
"filterSubscription": ["${SUBSCRIPTION_ID}"],
|
|
162
|
+
"from": "2026-03-01T00:00:00Z",
|
|
163
|
+
"to": "2026-12-31T23:59:59Z",
|
|
164
|
+
"windowSize": "DAY"
|
|
165
|
+
}
|
|
166
|
+
EOF
|
|
166
167
|
```
|
|
167
168
|
|
|
168
169
|
### Test with cURL
|
|
@@ -171,7 +172,7 @@ Verify the full flow manually:
|
|
|
171
172
|
|
|
172
173
|
```bash
|
|
173
174
|
# Make a request to a monetized endpoint
|
|
174
|
-
curl -v -H "Authorization: Bearer {CUSTOMER_API_KEY}" \
|
|
175
|
+
curl -v -H "Authorization: Bearer ${CUSTOMER_API_KEY}" \
|
|
175
176
|
https://your-api.zuplo.dev/api/v1/resource
|
|
176
177
|
|
|
177
178
|
# Check the response status and body for error details
|
|
@@ -195,30 +196,46 @@ products.
|
|
|
195
196
|
|
|
196
197
|
### What happens if Stripe is down?
|
|
197
198
|
|
|
198
|
-
Zuplo
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
Zuplo's API request path doesn't go through Stripe. The runtime validates each
|
|
200
|
+
request against Zuplo's own subscription store, so existing customers keep
|
|
201
|
+
access while Stripe is unavailable. New paid subscriptions and billing-affecting
|
|
202
|
+
plan changes do need Stripe and have to wait; free plans skip Stripe Checkout
|
|
203
|
+
entirely and still work. If Stripe misses scheduled payments during the outage,
|
|
204
|
+
the [grace period](./monetization-policy.md#subscription-and-payment-validation)
|
|
205
|
+
(default 3 days) absorbs them once Stripe recovers and reports the failures.
|
|
202
206
|
|
|
203
207
|
### Can I use currencies other than USD?
|
|
204
208
|
|
|
205
209
|
Yes. Plans support any ISO 4217 currency code. Set the `currency` field when
|
|
206
210
|
creating a plan. You can offer the same plan in multiple currencies by creating
|
|
207
|
-
separate plan objects (e.g., `
|
|
211
|
+
separate plan objects (e.g., `pro_usd` and `pro_eur`).
|
|
208
212
|
|
|
209
213
|
### How are overages calculated?
|
|
210
214
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
Overage uses graduated tiered pricing on a `usage_based` rate card. Every tier
|
|
216
|
+
has two price slots, `flatPrice` and `unitPrice` — either or both can be set
|
|
217
|
+
(each may be 0), and a tier charges its flat price plus its per-unit price times
|
|
218
|
+
the units consumed inside the tier. All tier-based charges are billed in arrears
|
|
219
|
+
at the end of the billing cycle. The entitlement must set `isSoftLimit: true`,
|
|
220
|
+
otherwise the policy returns 403 at the quota line and there's no overage to
|
|
221
|
+
bill.
|
|
222
|
+
|
|
223
|
+
For example, tier 1 with `flatPrice: $499` covers the first 1M requests; tier 2
|
|
224
|
+
with `unitPrice: $0.0005` covers each request after. A customer who used 1.2M
|
|
225
|
+
requests invoices $499 + (200,000 × $0.0005) = $599 at the end of the period.
|
|
226
|
+
|
|
227
|
+
If you need a fixed fee billed at the **start** of the cycle instead, add a
|
|
228
|
+
separate billing-only rate card alongside the usage-based one (no `featureKey`,
|
|
229
|
+
`paymentTerm: "in_advance"`) — see
|
|
230
|
+
[Base fee in advance](./billing-models.md#example-base-fee-in-advance). For the
|
|
231
|
+
full rate card shape, see
|
|
232
|
+
[Included Usage with Overage](./pricing-models.mdx#included-usage-with-overage).
|
|
215
233
|
|
|
216
234
|
### Can I set spending limits for pay-as-you-go customers?
|
|
217
235
|
|
|
218
236
|
You can set a hard entitlement limit that acts as a spending cap (e.g., max
|
|
219
237
|
100,000 requests regardless of willingness to pay). Alternatively, use a custom
|
|
220
|
-
policy to check current usage against a configurable limit
|
|
221
|
-
billing alerts to notify customers approaching a threshold.
|
|
238
|
+
policy to check current usage against a configurable limit.
|
|
222
239
|
|
|
223
240
|
### Do meters count retried requests?
|
|
224
241
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "6.70.
|
|
3
|
+
"version": "6.70.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The programmable API Gateway",
|
|
6
6
|
"author": "Zuplo, Inc.",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"zuplo": "zuplo.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zuplo/cli": "6.70.
|
|
23
|
-
"@zuplo/core": "6.70.
|
|
24
|
-
"@zuplo/runtime": "6.70.
|
|
22
|
+
"@zuplo/cli": "6.70.28",
|
|
23
|
+
"@zuplo/core": "6.70.28",
|
|
24
|
+
"@zuplo/runtime": "6.70.28",
|
|
25
25
|
"@zuplo/test": "1.4.0"
|
|
26
26
|
}
|
|
27
27
|
}
|