Add self-service API key usage status (#2908)

Integrated into release/v3.8.6
This commit is contained in:
guanbear
2026-05-30 00:16:53 +08:00
committed by diegosouzapw
parent 8b74fb48ca
commit fbe140c231
61 changed files with 2179 additions and 9 deletions

View File

@@ -0,0 +1,65 @@
# Change: Self-Service API Key Usage and Quota Visibility
## Summary
Add a client-facing self-service status endpoint and dashboard controls that let each OmniRoute API key inspect its own USD usage, token usage, and percent used against its existing USD budget configuration. Optionally expose shared upstream account quota when an operator grants a dedicated per-key scope.
## Motivation
OmniRoute can route multiple delegated API keys through one upstream coding account. Operators need per-key accountability without giving every delegated key management access. Existing management usage APIs are too broad for delegated clients because they can expose other keys and operational state.
This change creates a narrow own-key API and UI controls:
- Own cost and token usage are visible by default for ordinary new keys.
- Shared account quota remains opt-in because it is account-level and sensitive.
- USD budgets remain the enforcement mechanism; token totals are reporting only.
## Scope
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.
- 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.
- Tests and docs for the new behavior.
Out of scope:
- Token quota enforcement.
- Cross-key reporting through the self-service endpoint.
- Changing management usage APIs.
- Changing provider routing or quota preflight behavior.
- Raw upstream quota payload exposure.
- A second budget editor inside key creation or permissions dialogs.
## Compatibility
Existing keys should continue to work. A migration or first-start normalization step should backfill `self:usage` onto existing ordinary keys so they receive the same default own-usage visibility as newly created keys. Existing keys must not receive shared account quota visibility unless `self:account-quota` is explicitly granted.
The new scopes must not grant management access. Only `manage` and `admin` remain management-grade.
## Risks
- 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.
- 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.
- The current `/api/usage/budget` route relies on route-level authz rather than handler-level `requireManagementAuth()`, so the PR should harden it or explicitly test the proxy guard.
## Rollout
1. Add constants, validation, and helper tests.
2. Raise or otherwise adapt scope validation limits.
3. Add idempotent existing-key backfill for `self:usage`.
4. Harden `/api/usage/budget` with handler-level management auth or add explicit proxy-guard tests.
5. Add self-service status endpoint.
6. Add create/edit dashboard controls.
7. Add i18n message keys for dashboard text.
8. Add API/reference docs.
9. Verify against release branch used for upstream PR.

View File

@@ -0,0 +1,198 @@
# 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

View File

@@ -0,0 +1,71 @@
# Tasks
## 1. Scope and Validation
- [ ] Add `self:usage` and `self:account-quota` constants outside management scopes.
- [ ] Extend key creation validation to accept self-service scopes.
- [ ] Raise or replace the current 16-scope validation cap so new scopes do not break existing custom/MCP-heavy keys.
- [ ] Add an idempotent compatibility migration or startup normalization for existing keys.
- [ ] Add tests proving self-service scopes do not satisfy management auth.
## 2. Usage Aggregation
- [ ] Add helper to derive self-service status from authenticated API key metadata.
- [ ] Aggregate cost through existing `getCostSummary()` and `checkBudget()`.
- [ ] Aggregate token totals from `usage_history` by `api_key_id` and period start.
- [ ] Add tests for missing budget, configured budget, and token totals.
## 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.
## 4. API Endpoint
- [ ] Add `GET /api/v1/me/status`.
- [ ] Authenticate in the handler using a normal Bearer API key and derive the API key id from DB metadata.
- [ ] Reject anonymous access even when global client API auth would allow anonymous local traffic.
- [ ] Reject env-only management keys for this own-key endpoint.
- [ ] Reject missing/invalid keys with `401`.
- [ ] Reject keys without `self:usage` with `403` after compatibility backfill has run.
- [ ] Ignore any caller-supplied `apiKeyId`.
- [ ] Add route tests for isolation and response shape.
## 5. Dashboard
- [ ] Add create-key controls for own usage visibility and shared account quota visibility.
- [ ] Add edit-permissions controls for self-service visibility.
- [ ] Reuse the existing budget configuration surface for USD limit editing.
- [ ] Preserve unrelated scopes when editing permissions.
- [ ] Show per-key budget percent and token totals in the key details experience.
- [ ] Show no-budget state as not configured while still showing usage.
- [ ] Add UI tests for defaults and scope preservation.
## 6. Internationalization
- [ ] Add translation keys under the existing API Manager namespace for all new UI text.
- [ ] Update default and generated locale message files according to the repo's i18n workflow.
- [ ] Add or run a translation key consistency check.
- [ ] Run `npm run i18n:sync-ui:dry`.
- [ ] Run `npm run i18n:check-ui-coverage`.
## 7. Budget Endpoint Hardening
- [ ] Add handler-level management auth to `/api/usage/budget` GET and POST, or document and test why proxy-only protection is intentional.
- [ ] Add a regression test proving ordinary self-service keys cannot use `/api/usage/budget?apiKeyId=...` to read arbitrary keys.
## 8. Documentation
- [ ] Add API reference entry for `/api/v1/me/status`.
- [ ] Update user guide/API manager docs.
- [ ] Document privacy behavior for shared account quota.
- [ ] Add migration/compatibility note for existing keys.
## 9. Verification
- [ ] Run lint.
- [ ] Run typecheck.
- [ ] Run focused unit/API/UI tests.
- [ ] Run coverage or the repo-required validation command before PR.