---
id: w4/m13/t002
title: Metadata writes — created-by at mint; throttled last-used on introspection
worker: worker4
status: done
estimate: 30m
depends_on: []
---

## Objective

Record who minted each key and when it was last used. `created-by` = the resolved caller subject (`api.IdentityFrom`) stamped at mint time in the apikeys service. `last-used` = updated when a key's token authenticates a request — **off the hot path**: an async, per-key-throttled update (at most one write per key per minute; in-memory last-write cache), because a synchronous store write per API request is a self-inflicted outage waiting to happen.

## Context

- Mint path: `lego/backend/internal/apikeys/service.go` (Hydra client creation, `bex.co/api-key`-stamped).
- Auth path where usage is observable: `internal/api/auth.go` (introspection results carry `client_id`).
- Storage: Hydra client metadata field vs a store table — prefer Hydra client metadata for `created-by` (travels with the client); `last-used` needs bex-side storage (store table when `BEX_CP_DB_URI`, else in-memory best-effort — document the degradation honestly).

## Steps

1. Stamp `created-by` into the Hydra client metadata at mint.
2. Usage observer: throttled async `last-used` recorder keyed by `client_id`; store-backed when available.
3. Document the store-less degradation (last-used resets on pod restart) in docs/ADR012-auth.md.

## Files

- `lego/backend/internal/apikeys/service.go`
- `lego/backend/internal/api/auth.go`
- `lego/backend/internal/store/` (last_used table/column + migration, store mode)

## Acceptance criteria

- [x] Minted key carries the creator subject; a request with the key updates `last-used` within a minute; request latency unaffected (no synchronous write).
- [x] Store-less mode degrades to documented best-effort, never errors.

## Out of scope

- Surfacing (t003); rotation/expiry policy built on this data (future).
