# 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: Allowed provider quotas shown with explicit permission

- GIVEN a valid API key has `self:account-quota`
- 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 an `accountQuotas` entry for each allowed provider-limit connection
- AND each window SHALL include used percentage, remaining percentage, and reset timestamp when known

#### Scenario: Single connection compatibility field

- GIVEN a valid API key has `self:account-quota`
- AND it is allowed to use exactly one provider-limit connection
- WHEN it calls the self-service endpoint
- 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 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 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

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
