Repository navigation
API keys: show when each key was last used #1138
Description
Context
API users have asked, in different ways, which of their keys are still in use. The API keys settings page shows each key's name and creation date, but nothing about use, so people can't tell which keys are safe to delete or whether a key they think is retired is still making requests.
User intent: on the API keys page, each key shows when it was last used (or "Never used"), so a user can clean up stale keys and spot unexpected use.
This is the first step of a larger direction: the billing server owns per-key usage (this issue), then per-key spend and user-set budgets (#1139). The key secret stays in the enclave; only a non-secret key ID crosses the boundary.
Thanks to @benthecarman, whose #1123 got this started and whose UI work ("Last used …" / "Never used" on each key row) is a good model for the page. That PR records last_used_at in OpenSecret's database from the auth middleware. Looking at where this is headed, we'd rather have the billing server own per-key usage, so the same data can later power spend tracking and budgets without a second pipeline.
Likely approach
The split:
| Enclave (OpenSecret) | Billing server |
|---|---|
| Key secret and hash, key name | Per-key usage records and "last used" |
| Create, list, delete, authenticate | Reporting per-key usage to the signed-in user |
OpenSecret
- Add a random, non-secret
public_id UUIDtouser_api_keys, backfilled for existing keys. Not the serialid(enumerable, leaks key counts) and notkey_hash(derived from the secret, never leaves the enclave). - Select
public_idin the existing API-key auth lookup (services/opensecret/src/db.rs,get_user_by_api_key_hash) and attach it to the request next to the user, so handlers know which key made the call. No extra query. - Add an optional
api_key_idto the SQSUsageEvent(services/opensecret/src/sqs.rs) and fill it inbuild_usage_event(services/opensecret/src/web/openai.rs) for API-key requests. - Return
public_idfrom the list API keys endpoint, and add it to theApiKeytypes in both SDKs.
Billing server (separate repository)
- Accept the optional
api_key_idon usage events and store it with each usage record. - Serve the signed-in user's per-key last-used time, e.g.
GET /v1/maple/api-keys/usage, behind the same token auth as the existing/v1/maple/*routes.
Research UI: list keys from OpenSecret, fetch usage from the billing server, and join on public_id. Keys the billing server has never seen show "Never used". If the billing call fails, the page still lists keys and omits the last-used line.
The new event field is optional on both sides, so OpenSecret and the billing server can deploy in either order.
Decisions to make
- What "last used" means. Only billed routes send usage events: chat completions, System One and embeddings. Speech, transcription and model listing don't, so keys used only for those would show "Never used". Options: count billed use only and label it that way (simplest), or send a zero-cost activity event from those routes.
publish_usage_event_internalskips zero-token events today. - Precision shown. Usage events are processed asynchronously, so "last used" can lag by however long the queue takes. A relative time ("2 hours ago") hides this; an exact timestamp invites questions.
- Repository guidance.
AGENTS.mdandservices/opensecret/AGENTS.mdsay usage truth stays in OpenSecret. Token counts still originate in OpenSecret and the billing server already computes cost from them, so this extends the existing split. The wording should be updated in the same change so it isn't read as a violation.
Acceptance criteria
- Each key on the API keys page shows its last-used time or "Never used".
- No key secret, key hash or key name leaves the enclave; only
public_idis sent to the billing server. - The API-key auth path makes no additional database writes or round trips.
- Requests rejected by billing or plan checks do not count as use (they publish no usage event).
- Existing keys get a
public_idby migration; new keys get one at creation. - An older SDK or client that ignores the new field keeps working.
Non-goals
- Per-key spend, totals or monthly breakdowns, and user-set budgets: API keys: per-key spend tracking and user-set budgets #1139.
- Showing usage to team admins for their members' keys.
Current implementation references
- API-key auth:
services/opensecret/src/web/openai_auth.rs(validate_openai_auth) - Key model and migration:
services/opensecret/src/models/user_api_keys.rs,services/opensecret/migrations/2025-08-21-173429_user_api_keys - Usage events:
services/opensecret/src/sqs.rs,publish_usage_event_internalinservices/opensecret/src/web/openai.rs - List/create endpoints:
services/opensecret/src/web/protected_routes.rs - UI:
apps/maple-research/frontend/src/components/settings/api/ApiKeysSettings.tsx - Earlier work: feat: show last-used timestamp on API keys settings page #1123
Activity