Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions docs/bdd/self-service-api-key-usage.feature
Original file line number Diff line number Diff line change
Expand Up @@ -61,36 +61,51 @@ Feature: Self-service API key usage and account quota visibility
Then the response status should be 200
And the response should not include shared account quota details

Scenario: Shared Codex account quota is visible with explicit permission
Scenario: Shared provider account quotas are visible with explicit permission
Given an API key named "team-a" has the scope "self:usage"
And "team-a" has the scope "self:account-quota"
And "team-a" is restricted to exactly one Codex connection
And "team-a" is restricted to a Codex connection and a Claude connection
And Codex reports a session quota with 1 percent used
And Claude reports a daily quota with 35 percent used
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
Then the response status should be 200
And the response accountQuotas should contain 2 entries
And the first response accountQuotas entry provider should be "codex"
And the first response accountQuotas entry quotas.session.remainingPercentage should be 99
And the second response accountQuotas entry provider should be "claude"
And the second response accountQuotas entry quotas.daily.remainingPercentage should be 65

Scenario: A single allowed provider also keeps the compatibility accountQuota field
Given an API key named "team-a" has the scope "self:usage"
And "team-a" has the scope "self:account-quota"
And "team-a" is restricted to exactly one Codex connection
And Codex reports a weekly quota with 97 percent used
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
Then the response status should be 200
And the response accountQuotas should contain 1 entry
And the response accountQuota.provider should be "codex"
And the response accountQuota.shared should be true
And the response accountQuota.quotas.session.remainingPercentage should be 99
And the response accountQuota.quotas.weekly.remainingPercentage should be 3

Scenario: Account quota is not guessed for multi-connection keys
Scenario: Unrestricted keys can see all active provider account quotas
Given an API key named "team-a" has the scope "self:usage"
And "team-a" has the scope "self:account-quota"
And "team-a" is allowed to use two provider connections
And "team-a" has no explicit allowed connection restrictions
And OmniRoute has active Codex and Cursor provider connections with quota data
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
Then the response status should be 200
And the response accountQuota.available should be false
And the response accountQuota.reason should be "ambiguous_connection"
And the response accountQuotas should contain the Codex account quota
And the response accountQuotas should contain the Cursor account quota

Scenario: Account quota is not guessed for unrestricted connection keys
Scenario: Provider connection lookup failures do not hide own usage
Given an API key named "team-a" has the scope "self:usage"
And "team-a" has the scope "self:account-quota"
And "team-a" has no explicit allowed connection restrictions
And "team-a" is restricted to a Codex connection and another provider connection
And OmniRoute cannot resolve the other provider connection metadata
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
Then the response status should be 200
And the response accountQuota.available should be false
And the response accountQuota.reason should be "ambiguous_connection"
And the response should still include own cost and token usage
And the unresolved response accountQuotas entry should have available false
And the unresolved response accountQuotas entry reason should be "connection_lookup_failed"

Scenario: Existing budget endpoint stays management-only
Given an API key named "team-a" has the scope "self:usage"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ In scope:
- New `GET /api/v1/me/status` endpoint authenticated by normal Bearer API key.
- New self-service API key scopes: `self:usage` and `self:account-quota`.
- Per-key cost and token aggregation for the calling key.
- Optional normalized provider account quota for unambiguous single-connection keys.
- Optional normalized provider account quotas for all provider-limit connections the key may use.
- API Manager create/edit controls for visibility scopes.
- Reuse the existing budget configuration surface for USD limits.
- i18n message keys for all new dashboard text.
Expand All @@ -46,7 +46,7 @@ The new scopes must not grant management access. Only `manage` and `admin` remai

- Scope editing in the current dashboard can collapse scopes to only management access; implementation must preserve unrelated scopes.
- Shared account quota can reveal account exhaustion; it must remain disabled by default.
- Multi-connection and unrestricted-connection keys are ambiguous; first implementation should decline account quota rather than guessing.
- Unrestricted keys can use all active provider connections, so account quota visibility enumerates all active provider-limit connections when explicitly permitted.
- Backfill must be idempotent so upgrades do not repeatedly rewrite API keys or re-enable a permission an operator later disabled.
- New UI text can regress non-English dashboards if translation keys are not added consistently.
- The current scope validation cap is 16 entries; adding self-service scopes may require raising that cap.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,30 +119,48 @@ The self-service endpoint SHALL include shared account quota only when the authe
- WHEN it calls the self-service endpoint
- THEN the response SHALL NOT include shared account quota details

#### Scenario: Codex quota shown with explicit permission
#### Scenario: Allowed provider quotas shown with explicit permission

- GIVEN a valid API key has `self:account-quota`
- AND it is restricted to exactly one Codex connection
- AND Codex quota data is available
- AND it is allowed to use Codex and Claude provider-limit connections
- AND quota data is available for both connections
- WHEN it calls the self-service endpoint
- THEN the response SHALL include normalized `session` and `weekly` quota windows
- THEN the response SHALL include an `accountQuotas` entry for each allowed provider-limit connection
- AND each window SHALL include used percentage, remaining percentage, and reset timestamp when known

#### Scenario: Multiple connections are ambiguous
#### Scenario: Single connection compatibility field

- GIVEN a valid API key has `self:account-quota`
- AND it is allowed to use more than one connection
- AND it is allowed to use exactly one provider-limit connection
- WHEN it calls the self-service endpoint
- THEN `accountQuota.available` SHALL be `false`
- AND `accountQuota.reason` SHALL be `ambiguous_connection`
- THEN the response SHALL include exactly one `accountQuotas` entry
- AND the response SHALL also include `accountQuota` with the same entry for backwards compatibility

#### Scenario: Unrestricted connections are ambiguous
#### Scenario: Unrestricted connections include active provider-limit connections

- GIVEN a valid API key has `self:account-quota`
- AND its `allowedConnections` list is empty, meaning all connections are allowed
- WHEN it calls the self-service endpoint
- THEN `accountQuota.available` SHALL be `false`
- AND `accountQuota.reason` SHALL be `ambiguous_connection`
- THEN the response SHALL include `accountQuotas` entries for active provider-limit connections

#### Scenario: Per-connection quota failure is isolated

- GIVEN a valid API key has `self:account-quota`
- AND it is allowed to use two provider-limit connections
- AND one provider quota fetch fails
- WHEN it calls the self-service endpoint
- THEN the successful provider SHALL remain in `accountQuotas`
- AND the failed provider SHALL be represented with `available: false` and `reason: "fetch_failed"`

#### Scenario: Provider connection lookup failure is isolated

- GIVEN a valid API key has `self:account-quota`
- AND it is explicitly allowed to use two provider-limit connections
- AND one provider connection lookup fails before quota fetching
- WHEN it calls the self-service endpoint
- THEN the successful provider SHALL remain in `accountQuotas`
- AND the unresolved connection SHALL be represented with `available: false` and `reason: "connection_lookup_failed"`
- AND the response SHALL still include the key's own cost and token usage

### Requirement: Dashboard configuration

Expand Down
7 changes: 4 additions & 3 deletions docs/openspec/changes/self-service-api-key-usage/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
## 3. Account Quota

- [ ] Resolve account quota only when the key has `self:account-quota`.
- [ ] Use exactly one explicit allowed connection; treat unrestricted or multiple connections as ambiguous.
- [ ] Normalize Codex quota windows to `session` and `weekly`.
- [ ] Add tests for no scope, one connection, multiple connections, unsupported provider, and fetch failure.
- [ ] Enumerate all explicit allowed connections, or all active connections when `allowedConnections` is empty.
- [ ] Normalize quota windows for every provider-limit connection that returns quota data.
- [ ] Preserve the legacy `accountQuota` field when exactly one quota entry is returned.
- [ ] Add tests for no scope, one connection, multiple connections, unrestricted connections, unsupported provider, and fetch failure.

## 4. API Endpoint

Expand Down
Loading
Loading