zuplo 7.6.3 → 7.6.5
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 +6 -6
- package/docs/ai-gateway/managing-apps.mdx +5 -4
- package/docs/ai-gateway/managing-teams.mdx +4 -3
- package/docs/ai-gateway/overview.mdx +10 -9
- package/docs/ai-gateway/policy-templates.mdx +17 -11
- package/docs/ai-gateway/teams.mdx +1 -1
- package/docs/ai-gateway/usage-limits.mdx +196 -47
- package/docs/articles/api-key-administration.mdx +2 -2
- package/docs/articles/api-key-api.mdx +26 -5
- package/docs/articles/api-key-authentication.mdx +1 -1
- package/docs/articles/api-key-buckets.mdx +27 -4
- package/docs/articles/api-key-consumer-bucket-portal-ui.mdx +13 -11
- package/docs/articles/api-key-leak-detection.mdx +2 -2
- package/docs/articles/api-key-self-serve-integration.mdx +19 -19
- package/docs/articles/audit-logs.mdx +11 -10
- package/docs/articles/monetization/api-access.mdx +1 -1
- package/docs/articles/monetization/going-to-production.mdx +1 -1
- package/docs/concepts/ai-gateway.mdx +4 -3
- package/docs/concepts/api-keys.md +1 -1
- package/docs/dedicated/akamai/ai-powered-applications.mdx +11 -8
- package/docs/dev-portal/dev-portal-create-consumer-on-auth.mdx +10 -5
- package/docs/policies/ai-gateway-configuration-executor-v2-inbound/doc.md +1 -1
- package/docs/policies/ai-gateway-metering-v2-inbound/schema.json +1 -1
- package/docs/policies/semantic-cache-inbound/schema.json +6 -2
- package/package.json +5 -5
|
@@ -98,17 +98,17 @@ account, Zuplo project, and AI Gateway team roles control access.
|
|
|
98
98
|
|
|
99
99
|
4. **Create a team**
|
|
100
100
|
|
|
101
|
-
Teams organize users and carry
|
|
102
|
-
|
|
101
|
+
Teams organize users and carry their own budget rules and policy templates.
|
|
102
|
+
Even if you're starting solo, you need at least one team.
|
|
103
103
|
|
|
104
104
|
Open the **Apps & Teams** tab and click **New Team**. Name your team (for
|
|
105
105
|
example, "Root" or your company name), choose an icon, and click **Create
|
|
106
106
|
Team**. This is your first team, so there's no parent picker yet—it appears
|
|
107
107
|
on later teams, letting you nest them. To set gateway-wide spending limits
|
|
108
|
-
across this Zuplo project, open the gateway's **Settings → Usage Limits
|
|
109
|
-
|
|
110
|
-
[Usage Limits](./usage-limits.mdx). Gateway and team limits
|
|
111
|
-
app regardless of its policy chain.
|
|
108
|
+
across this Zuplo project, open the gateway's **Settings → Usage Limits**.
|
|
109
|
+
Team budgets—shared and per-expression—are set on the team's **Usage &
|
|
110
|
+
Limits** tab. See [Usage Limits](./usage-limits.mdx). Gateway and team limits
|
|
111
|
+
apply to every app regardless of its policy chain.
|
|
112
112
|
|
|
113
113
|
5. **Create an app**
|
|
114
114
|
|
|
@@ -86,10 +86,11 @@ over—see [Dynamic model routing](./cookbooks/dynamic-model-routing.mdx).
|
|
|
86
86
|
## Setting budgets
|
|
87
87
|
|
|
88
88
|
App budgets live on the **Budgets and Costs** policy in the app's chain. Open
|
|
89
|
-
the app's **Policies** tab and
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
the app's **Policies** tab and add budget rules capping spending, tokens, or
|
|
90
|
+
requests over an hourly, daily, weekly, or monthly period—either for the app as
|
|
91
|
+
a whole or per metadata value, so each caller gets its own budget. Team and
|
|
92
|
+
gateway limits apply to every app automatically, even when the app's chain
|
|
93
|
+
doesn't include Budgets and Costs. See [Usage Limits](./usage-limits.mdx).
|
|
93
94
|
|
|
94
95
|
## Editing an App
|
|
95
96
|
|
|
@@ -39,10 +39,11 @@ click **Create Sub-Team** in the team's header.
|
|
|
39
39
|
|
|
40
40
|
After creating a team, use its tabs to configure it:
|
|
41
41
|
|
|
42
|
-
- **
|
|
42
|
+
- **Policy Template**—the team's [policy template](./policy-templates.mdx), the
|
|
43
43
|
starting policy chain for apps created in the team.
|
|
44
|
-
- **Usage & Limits**—
|
|
45
|
-
|
|
44
|
+
- **Usage & Limits**—the team's budgets, shared and per-expression, applying to
|
|
45
|
+
the team, its sub-teams, and their apps. See
|
|
46
|
+
[Usage Limits](./usage-limits.mdx).
|
|
46
47
|
- **Members**—who belongs to the team.
|
|
47
48
|
|
|
48
49
|
## Adding Team Members
|
|
@@ -30,9 +30,9 @@ Google, Mistral, xAI, and more) dynamically without modifying app code.
|
|
|
30
30
|
Configure model access through the gateway rather than hard coding it into your
|
|
31
31
|
apps.
|
|
32
32
|
|
|
33
|
-
**Cost Control**: Set spending limits at gateway, team, and app
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
**Cost Control**: Set independent spending limits at the gateway, team, and app
|
|
34
|
+
levels. Configure hourly, daily, weekly, or monthly rules that warn or block
|
|
35
|
+
when usage reaches a limit.
|
|
36
36
|
|
|
37
37
|
**Security & Compliance**: Apply guardrails to detect and block prompt injection
|
|
38
38
|
attempts and prevent PII leakage in both requests and responses through
|
|
@@ -99,13 +99,14 @@ a consistent starting pipeline. See [Policy Chains](./policy-chains.mdx) and
|
|
|
99
99
|
|
|
100
100
|
### Team Hierarchy & Budgets
|
|
101
101
|
|
|
102
|
-
Organize users into teams with hierarchical structures. Set budget
|
|
103
|
-
|
|
102
|
+
Organize users into teams with hierarchical structures. Set a budget at each
|
|
103
|
+
node. Each node totals its own usage and all descendant usage, and any exceeded
|
|
104
|
+
**Block** limit rejects the request:
|
|
104
105
|
|
|
105
|
-
- **Gateway**: Limits across the Zuplo project (for example, $1,000/day)
|
|
106
|
-
- **Teams**:
|
|
107
|
-
$500/day for the Credit Team)
|
|
108
|
-
- **Apps**:
|
|
106
|
+
- **Gateway**: Limits across the Zuplo project (for example, $1,000/day).
|
|
107
|
+
- **Teams**: Limits across the team, its sub-teams, and their apps (for example,
|
|
108
|
+
$500/day for the Credit Team).
|
|
109
|
+
- **Apps**: Limits for one app's traffic.
|
|
109
110
|
|
|
110
111
|
See [Usage Limits](./usage-limits.mdx).
|
|
111
112
|
|
|
@@ -34,11 +34,17 @@ Policies tab.
|
|
|
34
34
|
|
|
35
35
|
:::
|
|
36
36
|
|
|
37
|
+
Everything on the **Policy Template** tab is template content, budgets included:
|
|
38
|
+
a **Budgets and Costs** entry in the template sets the default budget each new
|
|
39
|
+
app starts with in its own chain, and doesn't enforce anything on the team. The
|
|
40
|
+
team's own budgets—shared and per-expression—live on the team's **Usage &
|
|
41
|
+
Limits** tab. See [Usage Limits](./usage-limits.mdx).
|
|
42
|
+
|
|
37
43
|
## Editing a template
|
|
38
44
|
|
|
39
|
-
Open a team and select its **
|
|
40
|
-
app chain editor: add policies from the gateway's declared menu, order
|
|
41
|
-
configure their options.
|
|
45
|
+
Open a team and select its **Policy Template** tab. The template editor works
|
|
46
|
+
like the app chain editor: add policies from the gateway's declared menu, order
|
|
47
|
+
them, and configure their options.
|
|
42
48
|
|
|
43
49
|
Each policy in the template carries two checkboxes that control what an app
|
|
44
50
|
starting from the template may do with that entry. Both start checked—a policy
|
|
@@ -55,14 +61,14 @@ by the team policy template".
|
|
|
55
61
|
## Inheriting from a parent team
|
|
56
62
|
|
|
57
63
|
A sub-team can use its parent team's template instead of defining its own,
|
|
58
|
-
controlled by the **Inherit from parent** switch on the sub-team's
|
|
59
|
-
The switch drafts like the page's other edits and applies when
|
|
60
|
-
changes**. While inheriting, the sub-team's template is
|
|
61
|
-
parent's effective template. Turn the switch off to give
|
|
62
|
-
template: the editor starts from a copy of the parent's
|
|
63
|
-
which you then adjust. Turning inheritance back on and
|
|
64
|
-
sub-team's own template in favor of the parent's—before you
|
|
65
|
-
switch again to restore your draft.
|
|
64
|
+
controlled by the **Inherit from parent** switch on the sub-team's **Policy
|
|
65
|
+
Template** tab. The switch drafts like the page's other edits and applies when
|
|
66
|
+
you click **Save changes**. While inheriting, the sub-team's template is
|
|
67
|
+
read-only—it shows the parent's effective template. Turn the switch off to give
|
|
68
|
+
the sub-team its own template: the editor starts from a copy of the parent's
|
|
69
|
+
effective template, which you then adjust. Turning inheritance back on and
|
|
70
|
+
saving discards the sub-team's own template in favor of the parent's—before you
|
|
71
|
+
save, flip the switch again to restore your draft.
|
|
66
72
|
|
|
67
73
|
## Next steps
|
|
68
74
|
|
|
@@ -18,7 +18,7 @@ on that member's role.
|
|
|
18
18
|
|
|
19
19
|
Teams also carry a [policy template](./policy-templates.mdx)—the policy chain
|
|
20
20
|
that apps created in the team start with—and [usage limits](./usage-limits.mdx)
|
|
21
|
-
that apply to
|
|
21
|
+
that apply to the team's own usage and all descendant sub-teams and apps.
|
|
22
22
|
|
|
23
23
|
## Members
|
|
24
24
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Usage Limits &
|
|
2
|
+
title: "Usage Limits & Budget Rules"
|
|
3
3
|
sidebar_label: "Usage Limits"
|
|
4
4
|
description:
|
|
5
|
-
Set
|
|
6
|
-
|
|
5
|
+
Set spending, token, and request budgets at the gateway, team, and app levels,
|
|
6
|
+
and give every metadata value its own separate budget using expression rules.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
The Zuplo AI Gateway provides hierarchical usage limits and budget controls to
|
|
@@ -12,40 +12,118 @@ gateway, team, and app levels.
|
|
|
12
12
|
|
|
13
13
|
## Budget Hierarchy
|
|
14
14
|
|
|
15
|
-
Limits at every level apply together
|
|
16
|
-
|
|
15
|
+
Limits at every level apply together. A request is blocked when the gateway, an
|
|
16
|
+
ancestor team, or the app is over a **Block** limit. The error identifies
|
|
17
|
+
whether a shared node budget or an expression-specific budget ran out:
|
|
17
18
|
|
|
18
19
|
- **Gateway** - Limits across the Zuplo project (for example, $1,000/day),
|
|
19
|
-
covering all teams and apps combined
|
|
20
|
-
- **Teams** -
|
|
21
|
-
example, $500/day for the Engineering team)
|
|
20
|
+
covering all teams, sub-teams, and apps combined
|
|
21
|
+
- **Teams** - Limits covering the team's own usage and every descendant sub-team
|
|
22
|
+
and app (for example, $500/day for the Engineering team)
|
|
22
23
|
- **Apps** - Per-app limits for granular control (for example, $10/day for a
|
|
23
24
|
hackathon app)
|
|
24
25
|
|
|
26
|
+
Budgets at different levels don't constrain each other's values, and app budgets
|
|
27
|
+
under a team may add up to more than the team's own budget - the team's budget
|
|
28
|
+
is the aggregate cap. For example, take a $100/month team containing a $50/month
|
|
29
|
+
sub-team with two apps capped at $25/month and $40/month. When the apps have
|
|
30
|
+
spent $40 and $10, the sub-team's aggregate reaches $50 and requests through
|
|
31
|
+
**both** apps are blocked - including the app that is still under its own $25
|
|
32
|
+
cap. Apps elsewhere in the $100/month team keep working until that team's own
|
|
33
|
+
aggregate reaches $100.
|
|
34
|
+
|
|
35
|
+
<Diagram direction="vertical" height="h-96">
|
|
36
|
+
<DiagramNode id="team" variant="green">
|
|
37
|
+
Team · spent $50 of $100
|
|
38
|
+
</DiagramNode>
|
|
39
|
+
<DiagramNode id="subteam" variant="red">
|
|
40
|
+
Sub-team · spent $50 of $50
|
|
41
|
+
</DiagramNode>
|
|
42
|
+
<DiagramNode id="appa" variant="red">
|
|
43
|
+
App A · spent $10 of $25
|
|
44
|
+
</DiagramNode>
|
|
45
|
+
<DiagramNode id="appb" variant="red">
|
|
46
|
+
App B · spent $40 of $40
|
|
47
|
+
</DiagramNode>
|
|
48
|
+
<DiagramNode id="other" variant="green">
|
|
49
|
+
Other apps in the team
|
|
50
|
+
</DiagramNode>
|
|
51
|
+
<DiagramEdge from="team" to="subteam" label="at its Block limit" />
|
|
52
|
+
<DiagramEdge from="team" to="other" label="still serving" />
|
|
53
|
+
<DiagramEdge from="subteam" to="appa" label="blocked — under its own cap" />
|
|
54
|
+
<DiagramEdge from="subteam" to="appb" label="blocked" />
|
|
55
|
+
</Diagram>
|
|
56
|
+
|
|
25
57
|
## Where limits are configured
|
|
26
58
|
|
|
27
|
-
| Level |
|
|
28
|
-
| ------- | --------------------------------------------------------------------------------- |
|
|
29
|
-
| Gateway | **Settings → Usage Limits** |
|
|
30
|
-
| Team | The team's **Usage & Limits** tab |
|
|
31
|
-
| App | The **Budgets and Costs** policy in the app's [policy chain](./policy-chains.mdx) |
|
|
59
|
+
| Level | Shared budget | Per-expression budgets |
|
|
60
|
+
| ------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
61
|
+
| Gateway | **Settings → Usage Limits** | Supported in configuration, but not authorable in the Portal |
|
|
62
|
+
| Team | The team's **Usage & Limits** tab | The team's **Usage & Limits** tab |
|
|
63
|
+
| App | The **Budgets and Costs** policy in the app's [policy chain](./policy-chains.mdx) | The **Budgets and Costs** policy |
|
|
64
|
+
|
|
65
|
+
Gateway and team limits apply independently to every descendant app; they aren't
|
|
66
|
+
copied into the app's configuration. Each node meters its own usage plus all
|
|
67
|
+
descendant usage. A request is blocked if any node in its path is over a
|
|
68
|
+
**Block** limit. An app's own limits live on the Budgets and Costs policy.
|
|
69
|
+
|
|
70
|
+
:::note
|
|
71
|
+
|
|
72
|
+
Everything a team enforces lives on its **Usage & Limits** tab. The **Policy
|
|
73
|
+
Template** tab is only the template: a Budgets and Costs entry there is copied
|
|
74
|
+
into each new app as the app's own starting budget and doesn't enforce the
|
|
75
|
+
team's budget.
|
|
76
|
+
|
|
77
|
+
:::
|
|
78
|
+
|
|
79
|
+
## How a budget rule works
|
|
80
|
+
|
|
81
|
+
Every node's budgets are a list of rules. Each rule answers two questions—what
|
|
82
|
+
it budgets, and what the caps are.
|
|
83
|
+
|
|
84
|
+
**Scope** decides who shares the budget:
|
|
85
|
+
|
|
86
|
+
| Scope | What it budgets |
|
|
87
|
+
| ------------------------------------------ | ------------------------------------------------------------------------ |
|
|
88
|
+
| **Shared** (`budgetBy: "app"`) | One shared budget for the gateway, team, or app and its descendants |
|
|
89
|
+
| **By metadata** (`budgetBy: "expression"`) | A separate budget at that node for every distinct value of an expression |
|
|
90
|
+
|
|
91
|
+
A **By metadata** rule gives each distinct expression value its own allowance.
|
|
92
|
+
With the expression `request.headers.get("x-user")` and a $5 daily cap, every
|
|
93
|
+
distinct `x-user` value gets its own $5 per day—one user exhausting their budget
|
|
94
|
+
doesn't affect anyone else. A node can combine a shared rule with several
|
|
95
|
+
expression rules.
|
|
96
|
+
|
|
97
|
+
Ancestor rules of both kinds apply to every request through the node's subtree.
|
|
98
|
+
The difference is who gets blocked: a team's **Shared** rule blocks all of the
|
|
99
|
+
team's traffic once the team's aggregate is over, while a team's **By metadata**
|
|
100
|
+
rule meters each value across all of the team's apps combined and blocks only
|
|
101
|
+
the exhausted value—the same `x-user` shares one budget whether it calls App A
|
|
102
|
+
or App B.
|
|
103
|
+
|
|
104
|
+
**Limit rows** set the caps. Each row is a meter, a period, an amount, and an
|
|
105
|
+
action:
|
|
32
106
|
|
|
33
|
-
|
|
34
|
-
|
|
107
|
+
| Field | Values |
|
|
108
|
+
| ---------- | ----------------------------------------------------------------- |
|
|
109
|
+
| **Meter** | Cost (dollars), Tokens (input plus output), Requests |
|
|
110
|
+
| **Period** | Hourly, Daily, Weekly, Monthly |
|
|
111
|
+
| **Limit** | An absolute amount in the meter's own unit |
|
|
112
|
+
| **Action** | **Block** rejects the request; **Warn** notifies without blocking |
|
|
35
113
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
114
|
+
Add as many rows as you need to cap several meters and periods at once. A Warn
|
|
115
|
+
row and a Block row on the same meter and period give you warn-then-block—warn
|
|
116
|
+
at $80, block at $100. The warn amount has to be the lower of the two.
|
|
39
117
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
warning notification without blocking). Leave a field empty to not enforce it.
|
|
118
|
+
The **Overview** tab charts daily and monthly usage only. Hourly and weekly rows
|
|
119
|
+
are enforced, but no meter on that tab tracks them.
|
|
43
120
|
|
|
44
121
|
:::note
|
|
45
122
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
123
|
+
Periods are anchored to your gateway, not the calendar. Every period rolls over
|
|
124
|
+
relative to the time (UTC) the gateway was created, so a daily period resets at
|
|
125
|
+
that hour rather than at midnight, and a monthly one on that day of the month.
|
|
126
|
+
Teams and apps created later use the same anchor. Changing a limit mid-period
|
|
49
127
|
doesn't reset the period's usage.
|
|
50
128
|
|
|
51
129
|
:::
|
|
@@ -60,43 +138,114 @@ doesn't reset the period's usage.
|
|
|
60
138
|
1. On the **Policies** tab, configure the **Budgets and Costs** policy (add it
|
|
61
139
|
from **Add Policy** if the chain doesn't have it).
|
|
62
140
|
|
|
63
|
-
1.
|
|
64
|
-
|
|
141
|
+
1. Select **Add rule**, choose the scope, and use **Add limit** to add rows for
|
|
142
|
+
the meters and periods you want to cap. For a **By metadata** rule, enter the
|
|
143
|
+
expression that identifies each caller.
|
|
65
144
|
|
|
66
|
-
1. Save
|
|
145
|
+
1. Select **Apply**, then **Save changes** on the policy chain. The change
|
|
146
|
+
applies within about a minute.
|
|
67
147
|
|
|
68
148
|
</Stepper>
|
|
69
149
|
|
|
70
|
-
|
|
150
|
+
The app editor groups gateway and ancestor team rules under **Inherited**. Those
|
|
151
|
+
rules remain stored and enforced at the gateway or team; edit them where they
|
|
152
|
+
were created.
|
|
71
153
|
|
|
72
|
-
|
|
73
|
-
next to its number, and so does each warning threshold. A period enforces
|
|
74
|
-
nothing until `enabled` is `true` _and_ `limit` is set—options that set only
|
|
75
|
-
`limit` are silently inert. Watch for this when editing the policy's options
|
|
76
|
-
directly, such as in `config/policies.json` or a
|
|
77
|
-
[policy template](./policy-templates.mdx).
|
|
154
|
+
### Budget expressions
|
|
78
155
|
|
|
79
|
-
|
|
156
|
+
A **By metadata** rule identifies each caller with an expression evaluated per
|
|
157
|
+
request. The editor validates it as you type. Available values include:
|
|
80
158
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
159
|
+
- `request.headers.get("x-user")` and other request headers
|
|
160
|
+
- `request.user.sub` and `request.user.data.<key>` from the authenticated API
|
|
161
|
+
key
|
|
162
|
+
- `request.url`, `request.method`, `request.query`, `request.searchParams`, and
|
|
163
|
+
`request.params`
|
|
164
|
+
- `context.contextId`, `context.requestId`, `context.route`, and
|
|
165
|
+
`context.custom`
|
|
166
|
+
|
|
167
|
+
The editor labels each expression by where its value comes from, because that
|
|
168
|
+
decides how much the budget can be trusted. A **client-supplied** value—a header
|
|
169
|
+
or query parameter—is attribution, not enforcement: a caller who changes it gets
|
|
170
|
+
a fresh budget. Budget on `request.user.*`, labeled **verified**, when the
|
|
171
|
+
segment itself has to hold, and keep a shared-budget rule alongside it as the
|
|
172
|
+
hard cap.
|
|
85
173
|
|
|
86
|
-
:::
|
|
174
|
+
:::caution
|
|
87
175
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
176
|
+
Changing an expression starts a new budget. The old rule's usage stays with the
|
|
177
|
+
old expression, so every value's accounting restarts from zero. Expressions are
|
|
178
|
+
also case-sensitive: `get("X-User")` and `get("x-user")` read the same header
|
|
179
|
+
but budget separately.
|
|
91
180
|
|
|
92
181
|
:::
|
|
93
182
|
|
|
183
|
+
App rules are stored in `options.budgetRules` on the app's own Budgets and Costs
|
|
184
|
+
policy entry. For the app rule schema, see the
|
|
185
|
+
[Budgets and Costs policy reference](../policies/ai-gateway-metering-v2-inbound.mdx).
|
|
186
|
+
|
|
187
|
+
## Setting team and gateway limits
|
|
188
|
+
|
|
189
|
+
Team and gateway limits cover the node's own usage and all descendant usage.
|
|
190
|
+
They apply to every descendant app whether or not its chain includes Budgets and
|
|
191
|
+
Costs.
|
|
192
|
+
|
|
193
|
+
Team rules—shared and per-expression—are added on the team's **Usage & Limits**
|
|
194
|
+
tab, using the same meter, period, amount, and action rows as the app editor.
|
|
195
|
+
|
|
196
|
+
To add a shared gateway rule, open **Settings → Usage Limits**. The gateway root
|
|
197
|
+
supports per-expression rules in `metadata.budgetRules`, but the Portal doesn't
|
|
198
|
+
author them. It displays a stored expression rule as read-only and lets you
|
|
199
|
+
delete it.
|
|
200
|
+
|
|
201
|
+
Each `budgetBy: "app"` rule is a shared budget for the node where the rule is
|
|
202
|
+
stored. At the gateway or team level, `app` means that gateway or team node, not
|
|
203
|
+
an app policy configuration. A team's usage includes its sub-teams and apps, and
|
|
204
|
+
the gateway's usage includes the whole project.
|
|
205
|
+
|
|
206
|
+
Gateway and team rules are stored in `metadata.budgetRules` on that node. The
|
|
207
|
+
editor groups ancestor rules under **Inherited**, but each rule remains enforced
|
|
208
|
+
at the node where it was created.
|
|
209
|
+
|
|
210
|
+
## When a limit is exceeded
|
|
211
|
+
|
|
212
|
+
If the app has a **quota fallback** model configured, an exceeded limit routes
|
|
213
|
+
requests to that model instead of blocking—see
|
|
214
|
+
[Fallback Models](./fallback.mdx), where the fallback model is selected. This
|
|
215
|
+
applies to gateway and team limits as well as the app's own. The fallback's
|
|
216
|
+
usage still counts toward the limits.
|
|
217
|
+
|
|
218
|
+
Without a fallback, the request is rejected with `429 Too Many Requests` and a
|
|
219
|
+
`budget` object naming what ran out:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"type": "https://httpproblems.com/http-status/429",
|
|
224
|
+
"title": "Too Many Requests",
|
|
225
|
+
"detail": "Your daily cost budget has been exhausted (limit: 5).",
|
|
226
|
+
"budget": {
|
|
227
|
+
"scope": "dimension",
|
|
228
|
+
"dimensionedBy": "request.headers.get(\"x-user\")",
|
|
229
|
+
"ruleId": "3f2a9c1b",
|
|
230
|
+
"meter": "cost",
|
|
231
|
+
"window": "daily",
|
|
232
|
+
"limit": 5
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The `scope` tells the caller which budget they hit. It is `application` when the
|
|
238
|
+
gateway, a team, or the app exhausts a shared node budget. It is `dimension`
|
|
239
|
+
when a value exhausts an expression budget. Quote the `ruleId` when raising a
|
|
240
|
+
support request.
|
|
241
|
+
|
|
94
242
|
:::caution{title="Budgets fail open by default"}
|
|
95
243
|
|
|
96
|
-
The Budgets and Costs policy's `throwOnFailure` option
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
244
|
+
The Budgets and Costs policy's `throwOnFailure` option—**Fail closed when
|
|
245
|
+
metering is unavailable** in the editor—controls how an error while checking the
|
|
246
|
+
app's own limits is handled. It defaults to `false`, so the request continues
|
|
247
|
+
through the gateway. Set it to `true` to reject the request instead. Team and
|
|
248
|
+
gateway limit checks run independently of this policy.
|
|
100
249
|
|
|
101
250
|
:::
|
|
102
251
|
|
|
@@ -13,8 +13,8 @@ environments.
|
|
|
13
13
|

|
|
14
14
|
|
|
15
15
|
To view the bucket for a different environment, use the **Dev / Preview / Prod**
|
|
16
|
-
switcher at the top of the sidebar.
|
|
17
|
-
|
|
16
|
+
switcher at the top of the sidebar. Copy the active bucket's ID from the
|
|
17
|
+
**Bucket ID** pill under the **API Key Service** heading.
|
|
18
18
|
|
|
19
19
|
To add a new API Key Consumer, click **Create Consumer** and complete the form.
|
|
20
20
|
|
|
@@ -84,12 +84,33 @@ inside Zuplo)
|
|
|
84
84
|
```bash
|
|
85
85
|
# Your Zuplo Account Name
|
|
86
86
|
export ACCOUNT_NAME=my-account
|
|
87
|
-
# Your bucket
|
|
88
|
-
export
|
|
87
|
+
# Your bucket ID (Found in Services > API Keys > Consumers)
|
|
88
|
+
export BUCKET_ID=bckt_2HaK3odRyJle6NJSu8mzNKBC30BYxllxL
|
|
89
89
|
# Your Zuplo API Key (Found in Account Settings > Zuplo API Keys)
|
|
90
90
|
export API_KEY=zpka_YOUR_API_KEY
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
For more information about finding the bucket ID, see
|
|
94
|
+
[Bucket identifiers](./api-key-buckets.mdx#bucket-identifiers).
|
|
95
|
+
|
|
96
|
+
<details>
|
|
97
|
+
<summary>Legacy: identifying a bucket by name (deprecated)</summary>
|
|
98
|
+
|
|
99
|
+
The consumer, key, and manager endpoints also accept a bucket name in place of
|
|
100
|
+
the bucket ID, so existing scripts keep working:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export BUCKET_NAME=my-bucket
|
|
104
|
+
curl \
|
|
105
|
+
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_NAME/consumers \
|
|
106
|
+
--header "Authorization: Bearer $API_KEY"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The Zuplo Portal doesn't display bucket names, so use the bucket ID in new code.
|
|
110
|
+
When a value matches both a bucket name and a bucket ID, the name wins.
|
|
111
|
+
|
|
112
|
+
</details>
|
|
113
|
+
|
|
93
114
|
### Creating a Consumer with a Key
|
|
94
115
|
|
|
95
116
|
When creating a new Consumer, it's a good idea to include some useful metadata
|
|
@@ -101,7 +122,7 @@ external identifier that links this consumer to your internal data as a tag.
|
|
|
101
122
|
|
|
102
123
|
```shell
|
|
103
124
|
curl \
|
|
104
|
-
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$
|
|
125
|
+
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_ID/consumers?with-api-key=true \
|
|
105
126
|
--request POST \
|
|
106
127
|
--header "Content-type: application/json" \
|
|
107
128
|
--header "Authorization: Bearer $API_KEY" \
|
|
@@ -156,7 +177,7 @@ You can use this API Key to call your Zuplo Gateway that's protected by the
|
|
|
156
177
|
```shell
|
|
157
178
|
export ORG_ID=1234
|
|
158
179
|
curl \
|
|
159
|
-
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$
|
|
180
|
+
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_ID/consumers/?include-api-keys=true&key-format=visible&tag.orgId=$ORG_ID \
|
|
160
181
|
--header "Authorization: Bearer $API_KEY"
|
|
161
182
|
```
|
|
162
183
|
|
|
@@ -248,7 +269,7 @@ creates a new key without an expiration.
|
|
|
248
269
|
export ORG_ID=1234
|
|
249
270
|
export CONSUMER_NAME=my-consumer
|
|
250
271
|
curl \
|
|
251
|
-
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$
|
|
272
|
+
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_ID/consumers/$CONSUMER_NAME/roll-key?tag.orgId=$ORG_ID \
|
|
252
273
|
--request POST \
|
|
253
274
|
--header "Authorization: Bearer $API_KEY" \
|
|
254
275
|
--header "Content-Type: application/json" \
|
|
@@ -144,7 +144,7 @@ development.
|
|
|
144
144
|
Either locally or in CI/CD you can specify any API Key bucket on the
|
|
145
145
|
[API Key Authentication](../policies/api-key-inbound.mdx) policy by setting the
|
|
146
146
|
`bucketId` property. Every environment can then share a single bucket that holds
|
|
147
|
-
the consumers your tests need. Find a bucket's ID
|
|
147
|
+
the consumers your tests need. Find a bucket's ID on the **Bucket ID** pill on
|
|
148
148
|
the
|
|
149
149
|
[**Services → API Keys → Consumers**](https://portal.zuplo.com/+/account/project/services)
|
|
150
150
|
page in the Zuplo Portal. You can use the
|
|
@@ -21,6 +21,31 @@ Zuplo automatically creates three buckets for each project:
|
|
|
21
21
|
For more information on how environments relate to Git branches, see
|
|
22
22
|
[Branch-Based Deployments](./branch-based-deployments.mdx).
|
|
23
23
|
|
|
24
|
+
## Bucket identifiers
|
|
25
|
+
|
|
26
|
+
Every bucket has an ID that starts with `bckt_`, and a name. Use the bucket ID
|
|
27
|
+
whenever you identify a bucket, both in the
|
|
28
|
+
[Zuplo Developer API](./api-key-api.mdx) and in policy configuration. To copy
|
|
29
|
+
it, open
|
|
30
|
+
[**Services → API Keys → Consumers**](https://portal.zuplo.com/+/account/project/services)
|
|
31
|
+
in the Zuplo Portal and select the environment with the **Dev / Preview / Prod**
|
|
32
|
+
switcher. The **Bucket ID** pill sits beside the environment badge under the
|
|
33
|
+
**API Key Service** heading, with a copy button.
|
|
34
|
+
|
|
35
|
+
:::caution{title="Deprecated"}
|
|
36
|
+
|
|
37
|
+
The bucket name is a legacy identifier. The Zuplo Portal doesn't display it, and
|
|
38
|
+
the Developer API accepts it only for backward compatibility on the consumer,
|
|
39
|
+
key, and manager endpoints. Pass the bucket ID instead.
|
|
40
|
+
|
|
41
|
+
Three endpoints are the exception and still require the bucket name:
|
|
42
|
+
|
|
43
|
+
- `GET /v1/accounts/{accountName}/key-buckets/{bucketName}`
|
|
44
|
+
- `PATCH /v1/accounts/{accountName}/key-buckets/{bucketName}`
|
|
45
|
+
- `DELETE /v1/accounts/{accountName}/key-buckets/{bucketName}`
|
|
46
|
+
|
|
47
|
+
:::
|
|
48
|
+
|
|
24
49
|
## Custom bucket configuration
|
|
25
50
|
|
|
26
51
|
To use a custom bucket, specify its `bucketId` in your API Key policy options:
|
|
@@ -36,10 +61,8 @@ To use a custom bucket, specify its `bucketId` in your API Key policy options:
|
|
|
36
61
|
}
|
|
37
62
|
```
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
[
|
|
41
|
-
in the Zuplo Portal, select the environment with the **Dev / Preview / Prod**
|
|
42
|
-
switcher, then click **Bucket Details**.
|
|
64
|
+
For more information about finding a bucket ID, see
|
|
65
|
+
[Bucket identifiers](#bucket-identifiers).
|
|
43
66
|
|
|
44
67
|
When the configuration sets no bucket, the policy uses the default bucket for
|
|
45
68
|
the current environment.
|
|
@@ -65,20 +65,20 @@ For deeper detail, see [Buckets and Environments](./api-key-buckets.mdx).
|
|
|
65
65
|
1. Use the **Dev / Preview / Prod** switcher at the top of the sidebar to select
|
|
66
66
|
the environment whose bucket you want.
|
|
67
67
|
|
|
68
|
-
1.
|
|
69
|
-
|
|
68
|
+
1. Copy the active bucket's ID from the **Bucket ID** pill beside the
|
|
69
|
+
environment badge under the **API Key Service** heading.
|
|
70
70
|
|
|
71
71
|
</Stepper>
|
|
72
72
|
|
|
73
73
|
<ModalScreenshot size="md">
|
|
74
74
|
|
|
75
|
-

|
|
76
76
|
|
|
77
77
|
</ModalScreenshot>
|
|
78
78
|
|
|
79
|
-
**Bucket
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
The **Bucket ID** pill shows the ID of the bucket that's currently active for
|
|
80
|
+
the selected environment. Bucket IDs start with `bckt_` — this is the value the
|
|
81
|
+
API Key Authentication policy's `bucketId` option takes.
|
|
82
82
|
|
|
83
83
|
## Create a consumer in a specific bucket
|
|
84
84
|
|
|
@@ -157,7 +157,7 @@ the [Zuplo Developer API](./api-key-api.mdx):
|
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
159
|
curl \
|
|
160
|
-
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$
|
|
160
|
+
https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_ID/consumers?with-api-key=true \
|
|
161
161
|
--request POST \
|
|
162
162
|
--header "Content-type: application/json" \
|
|
163
163
|
--header "Authorization: Bearer $ZAPI_KEY" \
|
|
@@ -168,10 +168,12 @@ curl \
|
|
|
168
168
|
}'
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
Replace `$ACCOUNT_NAME` with your Zuplo account name, `$
|
|
172
|
-
target bucket
|
|
173
|
-
[Zuplo API key](./accounts/zuplo-api-keys.mdx).
|
|
174
|
-
|
|
171
|
+
Replace `$ACCOUNT_NAME` with your Zuplo account name, `$BUCKET_ID` with the
|
|
172
|
+
target bucket's ID, and `$ZAPI_KEY` with your
|
|
173
|
+
[Zuplo API key](./accounts/zuplo-api-keys.mdx). The bucket name also works here,
|
|
174
|
+
but it's deprecated. For more information, see
|
|
175
|
+
[Bucket identifiers](./api-key-buckets.mdx#bucket-identifiers). Full reference
|
|
176
|
+
at the [Developer API documentation](https://dev.zuplo.com/docs).
|
|
175
177
|
|
|
176
178
|
## Troubleshooting
|
|
177
179
|
|
|
@@ -64,12 +64,12 @@ their integration, or omit it to revoke the old key immediately.
|
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
export ACCOUNT_NAME="your-account-name"
|
|
67
|
-
export
|
|
67
|
+
export BUCKET_ID="your-bucket-id"
|
|
68
68
|
export CONSUMER_NAME="your-consumer-name"
|
|
69
69
|
export ZUPLO_API_KEY="your-zuplo-api-key"
|
|
70
70
|
|
|
71
71
|
curl --request POST \
|
|
72
|
-
--url https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$
|
|
72
|
+
--url https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_ID/consumers/$CONSUMER_NAME/roll-key \
|
|
73
73
|
--header 'Authorization: Bearer $ZUPLO_API_KEY' \
|
|
74
74
|
--header 'Content-Type: application/json' \
|
|
75
75
|
--data '
|
|
@@ -51,12 +51,12 @@ Before you start, you need:
|
|
|
51
51
|
under
|
|
52
52
|
[**Account Settings → Zuplo API Keys**](https://portal.zuplo.com/+/account/settings/api-keys).
|
|
53
53
|
[More information](./accounts/zuplo-api-keys).
|
|
54
|
-
- Your **account name** and **bucket
|
|
54
|
+
- Your **account name** and **bucket ID**. A bucket groups consumers for an
|
|
55
55
|
environment - each project has buckets for production, preview, and
|
|
56
|
-
development.
|
|
56
|
+
development. Copy the bucket ID from the **Bucket ID** pill next to the **API
|
|
57
|
+
Key Service** heading on your project's
|
|
57
58
|
[**Services → API Keys → Consumers**](https://portal.zuplo.com/+/account/project/services)
|
|
58
|
-
page
|
|
59
|
-
the account name in
|
|
59
|
+
page, and the account name from
|
|
60
60
|
[**Project Settings → General**](https://portal.zuplo.com/+/account/project/settings/general).
|
|
61
61
|
- An application with server-side code and existing user authentication.
|
|
62
62
|
|
|
@@ -65,8 +65,8 @@ All examples in this guide use these environment variables:
|
|
|
65
65
|
```bash
|
|
66
66
|
# Your Zuplo Account Name
|
|
67
67
|
export ZUPLO_ACCOUNT=my-account
|
|
68
|
-
# Your bucket
|
|
69
|
-
export
|
|
68
|
+
# Your bucket ID (found on the Services → API Keys → Consumers page)
|
|
69
|
+
export ZUPLO_BUCKET_ID=bckt_2HaK3odRyJle6NJSu8mzNKBC30BYxllxL
|
|
70
70
|
# Your Zuplo API Key (found in Account Settings > Zuplo API Keys)
|
|
71
71
|
export ZUPLO_API_KEY=zpka_YOUR_API_KEY
|
|
72
72
|
```
|
|
@@ -128,7 +128,7 @@ initial API key in a single call by passing `?with-api-key=true`:
|
|
|
128
128
|
|
|
129
129
|
```shell
|
|
130
130
|
curl \
|
|
131
|
-
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
131
|
+
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers?with-api-key=true \
|
|
132
132
|
--request POST \
|
|
133
133
|
--header "Content-Type: application/json" \
|
|
134
134
|
--header "Authorization: Bearer $ZUPLO_API_KEY" \
|
|
@@ -189,7 +189,7 @@ To render an "API Keys" page in your settings, fetch the consumer's keys:
|
|
|
189
189
|
|
|
190
190
|
```shell
|
|
191
191
|
curl \
|
|
192
|
-
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
192
|
+
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers/org_123/keys?key-format=masked \
|
|
193
193
|
--header "Authorization: Bearer $ZUPLO_API_KEY"
|
|
194
194
|
```
|
|
195
195
|
|
|
@@ -224,7 +224,7 @@ staging and production), create a key directly:
|
|
|
224
224
|
|
|
225
225
|
```shell
|
|
226
226
|
curl \
|
|
227
|
-
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
227
|
+
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers/org_123/keys \
|
|
228
228
|
--request POST \
|
|
229
229
|
--header "Content-Type: application/json" \
|
|
230
230
|
--header "Authorization: Bearer $ZUPLO_API_KEY" \
|
|
@@ -238,7 +238,7 @@ the user a transition period to switch over. Use the roll-key endpoint:
|
|
|
238
238
|
|
|
239
239
|
```shell
|
|
240
240
|
curl \
|
|
241
|
-
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
241
|
+
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers/org_123/roll-key \
|
|
242
242
|
--request POST \
|
|
243
243
|
--header "Content-Type: application/json" \
|
|
244
244
|
--header "Authorization: Bearer $ZUPLO_API_KEY" \
|
|
@@ -261,7 +261,7 @@ To let users revoke a specific key immediately:
|
|
|
261
261
|
|
|
262
262
|
```shell
|
|
263
263
|
curl \
|
|
264
|
-
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
264
|
+
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers/org_123/keys/key_AM7eAiR0BiaXTam951XmC9kK \
|
|
265
265
|
--request DELETE \
|
|
266
266
|
--header "Authorization: Bearer $ZUPLO_API_KEY"
|
|
267
267
|
```
|
|
@@ -282,7 +282,7 @@ runtime, patch the consumer:
|
|
|
282
282
|
|
|
283
283
|
```shell
|
|
284
284
|
curl \
|
|
285
|
-
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
285
|
+
https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers/org_123 \
|
|
286
286
|
--request PATCH \
|
|
287
287
|
--header "Content-Type: application/json" \
|
|
288
288
|
--header "Authorization: Bearer $ZUPLO_API_KEY" \
|
|
@@ -305,12 +305,12 @@ append `?tag.orgId=org_123` to every call:
|
|
|
305
305
|
```shell
|
|
306
306
|
# List only consumers belonging to org_123
|
|
307
307
|
curl \
|
|
308
|
-
"https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
308
|
+
"https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers?tag.orgId=org_123&include-api-keys=true&key-format=masked" \
|
|
309
309
|
--header "Authorization: Bearer $ZUPLO_API_KEY"
|
|
310
310
|
|
|
311
311
|
# Delete a consumer - fails if the consumer doesn't have tag orgId=org_123
|
|
312
312
|
curl \
|
|
313
|
-
"https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$
|
|
313
|
+
"https://dev.zuplo.com/v1/accounts/$ZUPLO_ACCOUNT/key-buckets/$ZUPLO_BUCKET_ID/consumers/org_123?tag.orgId=org_123" \
|
|
314
314
|
--request DELETE \
|
|
315
315
|
--header "Authorization: Bearer $ZUPLO_API_KEY"
|
|
316
316
|
```
|
|
@@ -333,7 +333,7 @@ app.use(express.json());
|
|
|
333
333
|
|
|
334
334
|
const ZUPLO_BASE = "https://dev.zuplo.com/v1/accounts";
|
|
335
335
|
const ZUPLO_ACCOUNT = process.env.ZUPLO_ACCOUNT;
|
|
336
|
-
const
|
|
336
|
+
const ZUPLO_BUCKET_ID = process.env.ZUPLO_BUCKET_ID;
|
|
337
337
|
const ZUPLO_API_KEY = process.env.ZUPLO_API_KEY;
|
|
338
338
|
|
|
339
339
|
// TODO: Replace with your real auth middleware
|
|
@@ -351,7 +351,7 @@ app.get("/api/keys", async (req, res) => {
|
|
|
351
351
|
if (!orgId) return res.status(401).json({ error: "Unauthorized" });
|
|
352
352
|
|
|
353
353
|
const response = await fetch(
|
|
354
|
-
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${
|
|
354
|
+
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${ZUPLO_BUCKET_ID}/consumers/${orgId}/keys?key-format=masked`,
|
|
355
355
|
{
|
|
356
356
|
headers: { Authorization: `Bearer ${ZUPLO_API_KEY}` },
|
|
357
357
|
},
|
|
@@ -366,7 +366,7 @@ app.post("/api/keys", async (req, res) => {
|
|
|
366
366
|
if (!orgId) return res.status(401).json({ error: "Unauthorized" });
|
|
367
367
|
|
|
368
368
|
const response = await fetch(
|
|
369
|
-
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${
|
|
369
|
+
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${ZUPLO_BUCKET_ID}/consumers/${orgId}/keys`,
|
|
370
370
|
{
|
|
371
371
|
method: "POST",
|
|
372
372
|
headers: {
|
|
@@ -386,7 +386,7 @@ app.post("/api/keys/rotate", async (req, res) => {
|
|
|
386
386
|
if (!orgId) return res.status(401).json({ error: "Unauthorized" });
|
|
387
387
|
|
|
388
388
|
const response = await fetch(
|
|
389
|
-
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${
|
|
389
|
+
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${ZUPLO_BUCKET_ID}/consumers/${orgId}/roll-key?tag.orgId=${orgId}`,
|
|
390
390
|
{
|
|
391
391
|
method: "POST",
|
|
392
392
|
headers: {
|
|
@@ -406,7 +406,7 @@ app.delete("/api/keys/:keyId", async (req, res) => {
|
|
|
406
406
|
if (!orgId) return res.status(401).json({ error: "Unauthorized" });
|
|
407
407
|
|
|
408
408
|
const response = await fetch(
|
|
409
|
-
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${
|
|
409
|
+
`${ZUPLO_BASE}/${ZUPLO_ACCOUNT}/key-buckets/${ZUPLO_BUCKET_ID}/consumers/${orgId}/keys/${req.params.keyId}?tag.orgId=${orgId}`,
|
|
410
410
|
{
|
|
411
411
|
method: "DELETE",
|
|
412
412
|
headers: { Authorization: `Bearer ${ZUPLO_API_KEY}` },
|
|
@@ -121,12 +121,13 @@ The events view lets you:
|
|
|
121
121
|
Everything in the portal is backed by the Zuplo API, so you can query audit logs
|
|
122
122
|
programmatically — for scripting, custom dashboards, or exporting to other
|
|
123
123
|
systems. Authenticate with a [Zuplo API key](./accounts/zuplo-api-keys.mdx) and
|
|
124
|
-
query events by bucket
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
query events by bucket ID. Copy the bucket ID from the copyable **Bucket ID**
|
|
125
|
+
pill under the **API Key Service** heading on the
|
|
126
|
+
[**Services → API Keys → Consumers**](https://portal.zuplo.com/+/account/project/services)
|
|
127
|
+
page in the portal (it's the same bucket used by the API Key Service).
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
|
-
curl "https://dev.zuplo.com/v1/audit-logs/$
|
|
130
|
+
curl "https://dev.zuplo.com/v1/audit-logs/$BUCKET_ID/events?type=com.zuplo.api.request&startDate=2026-07-01T00:00:00Z&endDate=2026-07-07T00:00:00Z" \
|
|
130
131
|
-H "Authorization: Bearer $ZUPLO_API_KEY"
|
|
131
132
|
```
|
|
132
133
|
|
|
@@ -163,10 +164,10 @@ Two endpoints are available:
|
|
|
163
164
|
- **Aggregated stats** — `GET /v1/audit-logs/{bucketId}/stats` returns top-N
|
|
164
165
|
event counts grouped by event type or actor.
|
|
165
166
|
|
|
166
|
-
The `bucketId` path parameter
|
|
167
|
-
Each query is limited to a 30-day window between `startDate` and
|
|
168
|
-
the [Audit Logs API reference](../api/audit-logs) for all
|
|
169
|
-
response schemas.
|
|
167
|
+
The `bucketId` path parameter takes the bucket ID; these endpoints don't accept
|
|
168
|
+
a bucket name. Each query is limited to a 30-day window between `startDate` and
|
|
169
|
+
`endDate`. See the [Audit Logs API reference](../api/audit-logs) for all
|
|
170
|
+
parameters and response schemas.
|
|
170
171
|
|
|
171
172
|
## Exporting audit logs to a SIEM
|
|
172
173
|
|
|
@@ -182,14 +183,14 @@ const BASE_URL = "https://dev.zuplo.com/v1/audit-logs";
|
|
|
182
183
|
// Run on a schedule (e.g. every 5 minutes). `startDate` is the checkpoint
|
|
183
184
|
// saved by the previous run; `endDate` is the current time.
|
|
184
185
|
async function exportAuditLogs(
|
|
185
|
-
|
|
186
|
+
bucketId: string,
|
|
186
187
|
startDate: string,
|
|
187
188
|
endDate: string,
|
|
188
189
|
) {
|
|
189
190
|
let offset = 0;
|
|
190
191
|
let hasMore = true;
|
|
191
192
|
while (hasMore) {
|
|
192
|
-
const url = new URL(`${BASE_URL}/${
|
|
193
|
+
const url = new URL(`${BASE_URL}/${bucketId}/events`);
|
|
193
194
|
url.searchParams.set("startDate", startDate);
|
|
194
195
|
url.searchParams.set("endDate", endDate);
|
|
195
196
|
url.searchParams.set("limit", "100");
|
|
@@ -37,7 +37,7 @@ are set in your terminal:
|
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
# Your Bucket ID (could be working-copy, preview, or production)
|
|
40
|
-
# (Copy it from Bucket
|
|
40
|
+
# (Copy it from the Bucket ID pill on Services > API Keys > Consumers — https://portal.zuplo.com/+/account/project/services)
|
|
41
41
|
export BUCKET_ID=your-bucket-id
|
|
42
42
|
# Your Zuplo API Key (Found in Account Settings > Zuplo API Keys —
|
|
43
43
|
# https://portal.zuplo.com/+/account/settings/api-keys)
|
|
@@ -459,7 +459,7 @@ To help the team resolve your issue quickly, include:
|
|
|
459
459
|
| ------------------ | ------------------------------------------------------ |
|
|
460
460
|
| Account slug | Your Zuplo account identifier |
|
|
461
461
|
| Project slug | The project with monetization enabled |
|
|
462
|
-
| Bucket ID | Bucket
|
|
462
|
+
| Bucket ID | The Bucket ID pill on Services → API Keys → Consumers |
|
|
463
463
|
| Stripe account ID | Stripe Dashboard → Settings, starts with `acct_` |
|
|
464
464
|
| Plan keys | The plan keys involved in the issue |
|
|
465
465
|
| Subscription ID | If the issue is subscription-specific |
|
|
@@ -21,9 +21,10 @@ Three building blocks organize an AI Gateway:
|
|
|
21
21
|
can route to. Administrators configure providers once, including any
|
|
22
22
|
[custom OpenAI-compatible providers](../ai-gateway/custom-providers.mdx), and
|
|
23
23
|
consumers never see the underlying credentials.
|
|
24
|
-
- **[Teams](../ai-gateway/teams.mdx)** group users and carry budget limits
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
- **[Teams](../ai-gateway/teams.mdx)** group users and carry budget limits. Each
|
|
25
|
+
team totals usage across its sub-teams and apps. A request must remain within
|
|
26
|
+
the gateway, ancestor team, and app limits. See
|
|
27
|
+
[Usage Limits](../ai-gateway/usage-limits.mdx).
|
|
27
28
|
- **[Apps](../ai-gateway/apps.mdx)** are the pieces of software that call the
|
|
28
29
|
gateway — a support chatbot is one app, an internal coding agent is another.
|
|
29
30
|
Each app gets its own gateway URL, its own Zuplo-managed API key, and its own
|
|
@@ -187,7 +187,7 @@ Each project has separate buckets for production, preview, and working copy
|
|
|
187
187
|
environments. This means API keys created in production don't work in preview,
|
|
188
188
|
and vice versa.
|
|
189
189
|
|
|
190
|
-
For testing, you can specify a custom bucket
|
|
190
|
+
For testing, you can specify a custom bucket ID on the
|
|
191
191
|
[API Key Authentication](../policies/api-key-inbound.mdx) policy to share keys
|
|
192
192
|
across environments. Enterprise customers can share buckets across projects or
|
|
193
193
|
accounts.
|
|
@@ -165,14 +165,17 @@ When the firewall detects a threat, it can take one of three actions:
|
|
|
165
165
|
|
|
166
166
|
## Cost and Usage Controls
|
|
167
167
|
|
|
168
|
-
The Zuplo AI Gateway provides
|
|
169
|
-
spending:
|
|
170
|
-
|
|
171
|
-
- **
|
|
172
|
-
usage
|
|
173
|
-
- **Team budgets** -
|
|
174
|
-
-
|
|
175
|
-
- **
|
|
168
|
+
The Zuplo AI Gateway provides budget controls at every level of the hierarchy to
|
|
169
|
+
manage AI spending:
|
|
170
|
+
|
|
171
|
+
- **Gateway budgets** - Hourly, daily, weekly, or monthly limits across all AI
|
|
172
|
+
Gateway usage.
|
|
173
|
+
- **Team budgets** - Shared or per-expression limits across a team, its
|
|
174
|
+
sub-teams, and their apps.
|
|
175
|
+
- **App budgets** - Shared or per-expression limits for one app.
|
|
176
|
+
- **Rate limiting** - Request throttling to prevent abuse.
|
|
177
|
+
|
|
178
|
+
For more information, see [Usage Limits](../../ai-gateway/usage-limits.mdx).
|
|
176
179
|
|
|
177
180
|
## Security Model
|
|
178
181
|
|
|
@@ -56,14 +56,19 @@ secret `API_KEY` and set the value.
|
|
|
56
56
|
|
|
57
57
|
:::info
|
|
58
58
|
|
|
59
|
-
In the code below set the variable `
|
|
60
|
-
your Zuplo Gateway.
|
|
59
|
+
In the code below set the variable `API_KEY_BUCKET_ID` to the ID of the bucket
|
|
60
|
+
being used by your Zuplo Gateway. Copy it from the **Bucket ID** pill on your
|
|
61
|
+
project's
|
|
62
|
+
[**Services → API Keys → Consumers**](https://portal.zuplo.com/+/account/project/services)
|
|
63
|
+
page, or
|
|
61
64
|
[list your buckets using the Developer API](https://dev.zuplo.com/docs/routes#apikeybucketsservice_list).
|
|
65
|
+
For more information, see
|
|
66
|
+
[Bucket identifiers](../articles/api-key-buckets.mdx#bucket-identifiers).
|
|
62
67
|
|
|
63
68
|
:::
|
|
64
69
|
|
|
65
70
|
Next, add the following code to your custom action. Be sure to replace the
|
|
66
|
-
placeholder values with your actual account and bucket
|
|
71
|
+
placeholder values with your actual account name and bucket ID. Click the
|
|
67
72
|
**Deploy** button when you are finished.
|
|
68
73
|
|
|
69
74
|
```ts
|
|
@@ -71,7 +76,7 @@ const { fetch } = require("undici");
|
|
|
71
76
|
const { randomUUID } = require("crypto");
|
|
72
77
|
|
|
73
78
|
const ZUPLO_ACCOUNT = "my-zuplo-account";
|
|
74
|
-
const
|
|
79
|
+
const API_KEY_BUCKET_ID = "bckt_2HaK3odRyJle6NJSu8mzNKBC30BYxllxL";
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
82
|
* Handler that will be called during the execution of a PostLogin flow.
|
|
@@ -100,7 +105,7 @@ exports.onExecutePostLogin = async (event, api) => {
|
|
|
100
105
|
try {
|
|
101
106
|
// Create the consumer
|
|
102
107
|
const response = await fetch(
|
|
103
|
-
`https://dev.zuplo.com/v1/accounts/${ZUPLO_ACCOUNT}/key-buckets/${
|
|
108
|
+
`https://dev.zuplo.com/v1/accounts/${ZUPLO_ACCOUNT}/key-buckets/${API_KEY_BUCKET_ID}/consumers?with-api-key=true`,
|
|
104
109
|
{
|
|
105
110
|
method: "POST",
|
|
106
111
|
body: JSON.stringify(body),
|
|
@@ -319,7 +319,7 @@ selected from the request. Each expression rule requires one or more meter rows:
|
|
|
319
319
|
| `meter` | `cost`, `requests`, `tokens` |
|
|
320
320
|
| `period` | `hourly`, `daily`, `weekly`, `monthly` |
|
|
321
321
|
| `action` | `warn` records a warning; `block` returns HTTP `429` |
|
|
322
|
-
| `value` | A finite number greater than
|
|
322
|
+
| `value` | A finite number greater than zero |
|
|
323
323
|
|
|
324
324
|
A rule cannot repeat the same meter, period, and action. When a meter and period
|
|
325
325
|
have both actions, the `warn` value must be less than the `block` value. The
|
|
@@ -61,8 +61,12 @@
|
|
|
61
61
|
},
|
|
62
62
|
"cacheByPropertyPath": {
|
|
63
63
|
"type": "string",
|
|
64
|
-
"description": "
|
|
65
|
-
"examples": [
|
|
64
|
+
"description": "A light path selector into the JSON request body to use as the cache key. Start with `.` for dot notation or `[` for a quoted key containing dots or slashes, for example `.userId`, `.data.customerId`, or `[\"https://example.com/cache-key\"]`. Only works with cacheBy=propertyPath.",
|
|
65
|
+
"examples": [
|
|
66
|
+
".userId",
|
|
67
|
+
".data.customerId",
|
|
68
|
+
"[\"https://example.com/cache-key\"]"
|
|
69
|
+
]
|
|
66
70
|
},
|
|
67
71
|
"cacheByFunction": {
|
|
68
72
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "7.6.
|
|
3
|
+
"version": "7.6.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The official Zuplo CLI for local development and platform management",
|
|
6
6
|
"homepage": "https://zuplo.com/docs/cli/overview",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"zuplo": "zuplo.js"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zuplo/cli": "7.6.
|
|
36
|
-
"@zuplo/core": "7.6.
|
|
37
|
-
"@zuplo/runtime": "7.6.
|
|
38
|
-
"@zuplo/test": "7.6.
|
|
35
|
+
"@zuplo/cli": "7.6.5",
|
|
36
|
+
"@zuplo/core": "7.6.5",
|
|
37
|
+
"@zuplo/runtime": "7.6.5",
|
|
38
|
+
"@zuplo/test": "7.6.5"
|
|
39
39
|
}
|
|
40
40
|
}
|