Files
OmniRoute/docs/openspec/changes/self-service-api-key-usage/specs/api-key-self-service-usage/spec.md
guanbear a15750d968 Add self-service API key usage status (#2908)
Integrated into release/v3.8.6
2026-05-29 13:16:53 -03:00

199 lines
7.8 KiB
Markdown

# Specification: API Key Self-Service Usage
## ADDED Requirements
### Requirement: Self-service status endpoint
OmniRoute SHALL provide `GET /api/v1/me/status` for a valid Bearer API key to retrieve status for that same API key.
#### Scenario: Valid key reads own status
- GIVEN a valid API key with own-usage visibility
- WHEN it calls `GET /api/v1/me/status`
- THEN the response status SHALL be `200`
- AND the response SHALL include the API key id and name
- AND the response SHALL include cost usage for that key
- AND the response SHALL include token usage for that key
#### Scenario: Invalid key is rejected
- GIVEN a missing or invalid Bearer token
- WHEN the caller calls `GET /api/v1/me/status`
- THEN the response status SHALL be `401`
#### Scenario: Anonymous client API mode does not bypass self-service auth
- GIVEN global client API auth allows anonymous local traffic
- WHEN a caller without a Bearer API key calls `GET /api/v1/me/status`
- THEN the response status SHALL be `401`
#### Scenario: Environment management key is not a self-service key
- GIVEN the deployment has an environment management key
- WHEN that key calls `GET /api/v1/me/status`
- THEN the response SHALL NOT expose delegated API key usage
### Requirement: Own-key isolation
The self-service endpoint SHALL derive the API key id from the authenticated Bearer key and SHALL NOT accept caller-supplied key ids for lookup.
#### Scenario: Caller tries to query another key
- GIVEN API key A and API key B both have usage
- WHEN API key A calls `GET /api/v1/me/status?apiKeyId=<key-b-id>`
- THEN the response SHALL contain only API key A identity and usage
- AND the response SHALL NOT contain API key B usage
### Requirement: USD budget status
The self-service endpoint SHALL report per-key USD budget usage using the existing budget system.
#### Scenario: Key has an active monthly budget
- GIVEN an API key has a monthly USD budget of `50`
- AND the key has current-period cost of `12.50`
- WHEN the key calls the self-service status endpoint
- THEN `usage.cost.limitUsd` SHALL be `50`
- AND `usage.cost.usedUsd` SHALL be `12.50`
- AND `usage.cost.usedPercent` SHALL be `25`
- AND `usage.cost.remainingUsd` SHALL be `37.50`
#### Scenario: Key has no budget
- GIVEN an API key has no configured budget
- WHEN the key calls the self-service status endpoint
- THEN `usage.cost.limitUsd` SHALL be `null`
- AND `usage.cost.usedPercent` SHALL be `null`
- AND cost and token totals SHALL still be returned for the default display period
### Requirement: Token usage reporting
The self-service endpoint SHALL report token totals from `usage_history` for the authenticated API key and selected reporting period.
#### Scenario: Token totals include all tracked categories
- GIVEN an API key has usage rows with input, output, cache read, cache creation, and reasoning tokens
- WHEN the key calls the self-service status endpoint
- THEN the response SHALL include each token category total
- AND `totalTokens` SHALL include all reported token categories
### Requirement: Self-service scopes
OmniRoute SHALL support `self:usage` and `self:account-quota` API key scopes. These scopes SHALL NOT grant management API access.
#### Scenario: Self-service scope is not management
- GIVEN an API key has `self:usage`
- AND it does not have `manage` or `admin`
- WHEN it calls a management usage endpoint
- THEN the response SHALL be forbidden
#### Scenario: New key defaults
- GIVEN an operator opens the create API key UI
- THEN own cost and token usage visibility SHALL be enabled by default
- AND shared account quota visibility SHALL be disabled by default
#### Scenario: Existing keys receive own-usage visibility on upgrade
- GIVEN an ordinary API key existed before this feature
- AND it does not have `self:usage`
- WHEN the compatibility migration or startup normalization runs
- THEN the API key SHALL have `self:usage`
- AND the API key SHALL NOT have `self:account-quota`
#### Scenario: Key without own-usage scope is denied
- GIVEN a valid API key does not have `self:usage`
- WHEN it calls `GET /api/v1/me/status`
- THEN the response status SHALL be `403`
### Requirement: Shared account quota permission
The self-service endpoint SHALL include shared account quota only when the authenticated key has `self:account-quota`.
#### Scenario: Account quota hidden by default
- GIVEN a valid API key has own-usage visibility
- AND it does not have `self:account-quota`
- WHEN it calls the self-service endpoint
- THEN the response SHALL NOT include shared account quota details
#### Scenario: Codex quota 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
- WHEN it calls the self-service endpoint
- THEN the response SHALL include normalized `session` and `weekly` quota windows
- AND each window SHALL include used percentage, remaining percentage, and reset timestamp when known
#### Scenario: Multiple connections are ambiguous
- GIVEN a valid API key has `self:account-quota`
- AND it is allowed to use more than one connection
- WHEN it calls the self-service endpoint
- THEN `accountQuota.available` SHALL be `false`
- AND `accountQuota.reason` SHALL be `ambiguous_connection`
#### Scenario: Unrestricted connections are ambiguous
- 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`
### Requirement: Dashboard configuration
The API Manager SHALL allow operators to configure self-service visibility and SHALL reuse the existing budget configuration surface for USD limits.
#### Scenario: Edit preserves unrelated scopes
- GIVEN an API key has scopes `["self:usage", "custom:scope"]`
- WHEN an operator enables shared account quota in the permissions UI
- THEN the saved scopes SHALL include `self:usage`
- AND the saved scopes SHALL include `self:account-quota`
- AND the saved scopes SHALL still include `custom:scope`
#### Scenario: Budget editing remains in existing budget UI
- GIVEN an operator wants to change a key's USD budget limit
- WHEN they use the dashboard
- THEN OmniRoute SHALL direct them to the existing budget configuration surface
- AND the create-key dialog SHALL NOT introduce a second budget editor
#### Scenario: No budget is displayed as not configured
- GIVEN an API key has no configured budget
- WHEN the API Manager shows self-service usage for that key
- THEN the UI SHALL show usage and token totals
- AND the budget limit, remaining amount, and percent SHALL be shown as not configured
### Requirement: Dashboard internationalization
All new API Manager text for self-service usage visibility, shared account quota visibility, and no-budget display SHALL use OmniRoute's existing i18n message system.
#### Scenario: New UI strings use translation keys
- GIVEN the API Manager renders the new self-service controls
- THEN labels, descriptions, tooltips, empty states, and errors SHALL come from translation keys
- AND no new user-visible dashboard text SHALL be hard-coded in the component
#### Scenario: Locale files stay structurally compatible
- GIVEN new API Manager translation keys are added
- WHEN the translation consistency check runs
- THEN supported locale message files SHALL have compatible key structure
### Requirement: Existing budget management remains protected
The existing `/api/usage/budget` management endpoint SHALL NOT become an own-key self-service data source.
#### Scenario: Self-service key cannot read arbitrary budget endpoint
- GIVEN an API key has `self:usage`
- AND it does not have `manage` or `admin`
- WHEN it calls `/api/usage/budget?apiKeyId=<another-key-id>`
- THEN the response SHALL be rejected by management auth