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.
@@ -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 hierarchical budget controls and policy
102
- templates. Even if you're starting solo, you need at least one team.
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
- for team-specific limits, use the team's **Usage & Limits** tab. See
110
- [Usage Limits](./usage-limits.mdx). Gateway and team limits apply to every
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 configure it with daily or monthly limits for
90
- spending, tokens, or requests. Team and gateway limits apply to every app
91
- automatically, even when the app's chain doesn't include Budgets and Costs. See
92
- [Usage Limits](./usage-limits.mdx).
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
- - **Policies**—the team's [policy template](./policy-templates.mdx), the
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**—spending limits that apply to all of the team's apps
45
- combined. See [Usage Limits](./usage-limits.mdx).
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 levels with
34
- hierarchical budgets that cascade down through your structure. Configure daily
35
- and monthly thresholds with enforcement or warning notifications.
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 limits at
103
- each level that cascade down:
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**: Team-specific limits that can't exceed parent limits (for example,
107
- $500/day for the Credit Team)
108
- - **Apps**: Per-app limits for granular control
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 **Policies** tab. The template editor works like the
40
- app chain editor: add policies from the gateway's declared menu, order them, and
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 Policies tab.
59
- The switch drafts like the page's other edits and applies when you click **Save
60
- changes**. While inheriting, the sub-team's template is read-only—it shows the
61
- parent's effective template. Turn the switch off to give the sub-team its own
62
- template: the editor starts from a copy of the parent's effective template,
63
- which you then adjust. Turning inheritance back on and saving discards the
64
- sub-team's own template in favor of the parent's—before you save, flip the
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 all of the team's apps combined.
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 & Thresholds"
2
+ title: "Usage Limits & Budget Rules"
3
3
  sidebar_label: "Usage Limits"
4
4
  description:
5
- Set hierarchical spending, token, and request limits at the gateway, team, and
6
- app levels, with warning thresholds and quota fallbacks.
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—a request is blocked when _any_ level's
16
- limit is exceeded, and the error names the level that blocked it:
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** - Team-specific limits covering all of the team's apps combined (for
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 | Where |
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
- Each level supports the same three meters, each with independent daily and
34
- monthly settings:
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
- - **Budget** - spend in dollars
37
- - **Tokens** - input plus output tokens
38
- - **Requests** - request count
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
- For each meter and period you can set a **Limit** (usage at or above it gets
41
- blocked) and a **Warning** threshold (a percentage of the limit that triggers a
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
- Daily and monthly periods are anchored to your gateway, not the calendar: a
47
- daily period rolls over at the time of day (UTC) the gateway was created, and a
48
- monthly period runs anchor-day to anchor-day. Changing a limit mid-period
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. Set daily or monthly limits for budget, tokens, or requests, and optional
64
- warning thresholds.
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. The change applies within about a minute.
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
- :::note
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
- Each period in the Budgets and Costs policy's options carries an `enabled` flag
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
- If the app has a **quota fallback** model configured, an exceeded limit routes
82
- requests to that model instead of blocking with a `429`—see
83
- [Fallback Models](./fallback.mdx). The fallback's usage still counts toward the
84
- limits.
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
- :::note{title="App, team, and gateway limits are configured separately"}
174
+ :::caution
87
175
 
88
- The Budgets and Costs policy configures and enforces an app's own limits. Team
89
- and gateway limits apply to every app automatically, whether or not the app's
90
- policy chain includes Budgets and Costs.
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 controls how an error
97
- while checking the app's own limits is handled. It defaults to `false`, so the
98
- request continues through the gateway. Set it to `true` to reject the request
99
- instead. Team and gateway limit checks run independently of this policy.
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
  ![The API Keys → Consumers page in the Services section, with the sidebar showing API Keys, Monetization, and Audit Logs, and the environment switcher set to Dev](../../public/media/api-key-administration/services-page.png)
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. Click **Bucket Details** under the **API Key
17
- Service** heading to copy the active bucket's name or ID.
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 name (Found in Services > API Key Service)
88
- export BUCKET_NAME=my-bucket
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/$BUCKET_NAME/consumers?with-api-key=true \
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/$BUCKET_NAME/consumers/?include-api-keys=true&key-format=visible&tag.orgId=$ORG_ID \
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/$BUCKET_NAME/consumers/$CONSUMER_NAME/roll-key?tag.orgId=$ORG_ID \
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 under **Bucket Details** on
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
- Bucket IDs start with `bckt_`. To find one, open
40
- [**Services → API Keys → Consumers**](https://portal.zuplo.com/+/account/project/services)
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. Click **Bucket Details** under the **API Key Service** heading to see the
69
- active bucket's name and ID, each with a copy button.
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
- ![The API Keys → Consumers page with the environment switcher set to Dev and the Bucket Details button under the API Key Service heading](../../public/media/api-key-consumer-bucket-portal-ui/services-page-dropdown.png)
75
+ ![The API Keys → Consumers page with the environment switcher set to Dev and the Bucket ID pill under the API Key Service heading](../../public/media/api-key-consumer-bucket-portal-ui/services-page-dropdown.png)
76
76
 
77
77
  </ModalScreenshot>
78
78
 
79
- **Bucket Details** shows the name and ID of the bucket that's currently active
80
- for the selected environment. Bucket IDs start with `bckt_` — this is the value
81
- the API Key Authentication policy's `bucketId` option takes.
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/$BUCKET_NAME/consumers?with-api-key=true \
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, `$BUCKET_NAME` with the
172
- target bucket name, and `$ZAPI_KEY` with your
173
- [Zuplo API key](./accounts/zuplo-api-keys.mdx). Full reference at the
174
- [Developer API documentation](https://dev.zuplo.com/docs).
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 BUCKET_NAME="your-bucket-name"
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/$BUCKET_NAME/consumers/$CONSUMER_NAME/roll-key \
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 name**. A bucket groups consumers for an
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. Find the bucket name on your project's
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 (under **Bucket Details**, next to the **API Key Service** heading), and
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 name (found on the Services → API Keys → Consumers page)
69
- export ZUPLO_BUCKET=my-bucket
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/$ZUPLO_BUCKET/consumers?with-api-key=true \
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/$ZUPLO_BUCKET/consumers/org_123/keys?key-format=masked \
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/$ZUPLO_BUCKET/consumers/org_123/keys \
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/$ZUPLO_BUCKET/consumers/org_123/roll-key \
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/$ZUPLO_BUCKET/consumers/org_123/keys/key_AM7eAiR0BiaXTam951XmC9kK \
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/$ZUPLO_BUCKET/consumers/org_123 \
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/$ZUPLO_BUCKET/consumers?tag.orgId=org_123&include-api-keys=true&key-format=masked" \
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/$ZUPLO_BUCKET/consumers/org_123?tag.orgId=org_123" \
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 ZUPLO_BUCKET = process.env.ZUPLO_BUCKET;
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/${ZUPLO_BUCKET}/consumers/${orgId}/keys?key-format=masked`,
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/${ZUPLO_BUCKET}/consumers/${orgId}/keys`,
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/${ZUPLO_BUCKET}/consumers/${orgId}/roll-key?tag.orgId=${orgId}`,
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/${ZUPLO_BUCKET}/consumers/${orgId}/keys/${req.params.keyId}?tag.orgId=${orgId}`,
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 name. Your bucket name is shown on the **API Keys →
125
- Consumers** page in the portal, as the copyable pill under the **API Key
126
- Service** heading (it's the same bucket used by the API Key Service).
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/$BUCKET_NAME/events?type=com.zuplo.api.request&startDate=2026-07-01T00:00:00Z&endDate=2026-07-07T00:00:00Z" \
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 accepts either the bucket ID or the bucket name.
167
- Each query is limited to a 30-day window between `startDate` and `endDate`. See
168
- the [Audit Logs API reference](../api/audit-logs) for all parameters and
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
- bucketName: string,
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}/${bucketName}/events`);
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 Details on Services > API Keys > Consumers — https://portal.zuplo.com/+/account/project/services)
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 Details on Services → API Keys → Consumers |
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 that
25
- cascade down the hierarchy: gateway-wide limits, then team limits, then
26
- per-app limits. See [Usage Limits](../ai-gateway/usage-limits.mdx).
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 name on the
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 hierarchical budget controls to manage AI
169
- spending:
170
-
171
- - **Organization limits** - Maximum daily and monthly spending across all AI
172
- usage
173
- - **Team budgets** - Allocated budgets for departments or customer segments
174
- - **Application limits** - Per-application or per-use-case cost controls
175
- - **Rate limiting** - Request throttling to prevent abuse
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 `BUCKET_NAME` to the bucket being used by
60
- your Zuplo Gateway. If you don't know the name of your bucket, you can
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 names. Click the
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 API_KEY_BUCKET = "my-bucket";
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/${API_KEY_BUCKET}/consumers?with-api-key=true`,
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 or equal to zero |
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
@@ -103,7 +103,7 @@
103
103
  },
104
104
  "value": {
105
105
  "type": "number",
106
- "minimum": 0
106
+ "exclusiveMinimum": 0
107
107
  },
108
108
  "action": {
109
109
  "type": "string",
@@ -61,8 +61,12 @@
61
61
  },
62
62
  "cacheByPropertyPath": {
63
63
  "type": "string",
64
- "description": "The path to the property in the request body (JSON) to use as cache key. For example '.userId' would read the 'userId' property from the request body. Only works with cacheBy=propertyPath.",
65
- "examples": [".userId", ".data.customerId"]
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",
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.3",
36
- "@zuplo/core": "7.6.3",
37
- "@zuplo/runtime": "7.6.3",
38
- "@zuplo/test": "7.6.3"
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
  }