Skip to content

Show when each API key was last used - #1140

Draft
AnthonyRonning wants to merge 1 commit into
masterfrom
claude/project-thread-hxyn76
Draft

AnthonyRonning wants to merge 1 commit into
masterfrom
claude/project-thread-hxyn76

Conversation

@AnthonyRonning

@AnthonyRonning AnthonyRonning commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Requested by T · project thread

Before: Settings → API keys lists each key's name and creation date, with no way to tell whether a key is still in use.

After: each key also shows when it was last used ("Last used Oct 11, 2026, 12:34 PM"), or "No use recorded yet". If usage can't be loaded, or the server predates this change, the list looks exactly as it does today.

Each API key gets a random, non-secret public_id, the only key identifier that leaves the enclave: billing tracks usage under that ID, while the key, its hash and its name stay in OpenSecret.

How: a migration adds a unique user_api_keys.public_id that defaults to gen_random_uuid(), so existing keys each get their own. API-key auth now carries it as AuthMethod::ApiKey { public_id }, and usage events from API-key requests include it as an optional api_key_id; app (JWT) requests leave it out. The key list and create responses return public_id, and the TypeScript and Rust SDK types gain it as an optional field, so they keep working against older servers (no SDK release here; the app reads it through its own type). The settings page reads billing's GET /v1/maple/api-keys and matches its entries to keys by public_id.

Big thanks to benthecarman, whose #1123 first brought "last used" to these keys. This keeps that feature and moves its bookkeeping to the billing side, as discussed there.

Billing side: MaplePrivacyLabs/maple-billing-server#120. Refs #1138. Per-key spend and budgets (#1139) are left for later; billing's response is one object per key so they can be added there.

Rollout order

Dev first, then Prod. Two steps must come in order; every other mix stays compatible.

  1. Billing side (MaplePrivacyLabs/maple-billing-server#120), its migration before its server. Shipping it before OpenSecret means no API-key usage goes unattributed; the reverse order is safe but loses attribution for requests made in between.
  2. OpenSecret migration before the new enclave. The new code reads public_id when it checks, lists or creates API keys, so on an unmigrated database those requests return 500; other app requests are unaffected. The old enclave ignores the new column, so the migration can run early. Filling the column rewrites user_api_keys once, and startup doesn't run migrations.
  3. OpenSecret enclave, after the usual signed PCR approval is published to both history locations so live clients accept it.
  4. Clients with the next Maple release. Until both servers are live, the page simply hides last used.

Mixed states stay compatible: usage events from app requests are unchanged, and billing ignores the new api_key_id until its side ships. Existing clients and both SDKs ignore the extra public_id in key responses, so no SDK release is needed. When one does ship, note that the Rust ApiKey and ApiKeyCreateResponse structs gain a field, which matters only to code that builds them by hand.

Rollback: either server can roll back at any time. Revert a migration only after its server is back on the old code, and avoid reverting the OpenSecret one once keys are in use: re-running it gives every key a new ID, which orphans its usage history.

Notes for review

  • "Last used" is the latest billed request (chat and embeddings). Use from before this ships, and requests that aren't billed, don't show up, so a key without any reads "No use recorded yet" rather than "Never used". Usage is recorded asynchronously and can trail a request briefly; the page says so.

Testing

  • OpenSecret: cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test (789 passed, 32 ignored), and the disposable-database helper with --redo-latest: 21 AEAD/database tests, 2 OAuth database tests and 8 callback tests passed, none skipped. That includes the new db_api_key_auth_attributes_requests_to_the_key_public_id, which authenticates a key through the real middleware against Postgres.
  • Rust SDK: fmt, clippy --all-targets --all-features -D warnings, cargo test --lib (118 passed) and docs.
  • TypeScript SDK: format:check, build and the credential-free tests (291 passed, 3 skipped).
  • Research frontend: scripts/ci/frontend.sh (format, lint with 0 errors, typecheck, and 1307 tests passed, including new settings-page and billing-client tests).
  • PR CI passed, including the encrypted SDK integration suite, which checks public_id on key create and list.

Environment differences: OpenSecret ran in a reduced Nix shell with the same locked nixpkgs, rust-overlay and rust-toolchain.toml, because the default shell's BWS input can't be downloaded in this sandbox, and the database helper ran its Postgres as a non-root user. The frontend script ran with the SDK shell's pinned bun 1.3.5 instead of .#ci. Not run: an in-app check against a running stack.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HhSn98TEgLj9EwtFekv8Qy


Generated by Claude Code


&lt;source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"&gt;&lt;source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"&gt;&lt;img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"&gt; <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1794297911&installation_model_id=441606&pr_number=1140&ref=codesmith_pr_footer&repository=MaplePrivacyLabs%2FMaple&return_to=https%3A%2F%2Fgithub.com%2FMaplePrivacyLabs%2FMaple%2Fpull%2F1140&signature=8f775fd0a6f8ef7a6b5d0c6bb8e408524758c6e601adc244e73ea09c314c3688"&gt;&lt;picture&gt;&lt;source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"&gt;&lt;source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"&gt;&lt;img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"&gt;
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Give each API key a random, non-secret public_id. OpenSecret sends it as
api_key_id in the usage event of API-key requests, and the API key
settings read each key's latest billed use from the billing server by
that ID. The key, its hash and its name stay in the enclave.

The TypeScript and Rust SDK key types gain an optional public_id, so
they keep working against servers that predate it.

Refs #1138

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhSn98TEgLj9EwtFekv8Qy
@github-actions

Copy link
Copy Markdown
Contributor

Maple development preview: https://a4779125.maple-ca8.pages.dev

Commit: 4600c3fc622fd12c48c7cba5eba4234e9de1c324

Uses development API, billing, flags and PCR configuration. Cloudflare Access applies.

This branch was successfully deployed

1 active deployment
pages-pr-1140 — 4600c3fc Deployed Oct 11, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants