Virtual keys¶
Outcome: Issue scoped keys with budgets, RPM, and TPM limits.
Steps¶
daari keys team-create eng --daily-budget 5 --rpm 120 --tpm 80000 --rpd 5000
daari keys create ci --daily-budget 2 --rpm 60 --tpm 40000 --rpd 2000 --team eng --window 7d=10
daari keys create shared-agent --user-daily-cap 2
daari keys list
daari keys list --status active
daari keys list --idle-days 90
daari report --by-team
daari usage --by-user
--rpd is requests per UTC day on the key or team. 0, or omitting the flag, means unlimited.
daari keys list --status filters active / expired / revoked. --idle-days N
returns keys whose last_used_at is older than N days (or never used). Successful
auth updates last_used_at (throttled to once per 60s per key).
Enable in config:
server:
virtual_keys:
enabled: true
Use the plaintext secret once as Authorization: Bearer ….
Model allowlists¶
Restrict a key or a team to specific models. Patterns are exact names or globs
(claude-*). Named groups are defined once in settings and referenced by
keys and teams. A key allowlist intersects the team allowlist: the key can
only narrow what the team already allows. Leave the field unset for today's
unrestricted behavior.
model_groups:
anthropic:
- claude-*
local:
- llama3.2:3b
daari keys team-create eng --model-group anthropic --allowed-model 'llama*'
daari keys create ci --team eng --allowed-model llama3.2:3b
daari keys update <key_id> --allowed-model 'claude-*'
daari keys team-update <team_id> --model-group local
A request whose model is outside the allowlist is HTTP 403
model_not_allowed (the body names the model, never the key) and writes an
auth.model_denied audit row. Router fallback will not send that key to a
frontier model outside the allowlist.
GET /v1/models (OpenAI and Anthropic facades) returns only cards that match
the key ∪ team allowlist when either side is set — same patterns as inference.
Master key and unrestricted virtual keys still see the full catalog.
GET /v1/models/{id} returns 404 for ids outside the allowlist. Lifecycle
query filters compose with the allowlist (retired models stay omitted by
default).
Admission priority¶
The global in-flight gate admits waiters by priority class, then FIFO:
high before normal before low. Mark interactive IDE keys high and
overnight batch / eval keys low so one GPU favors live turns. Key priority
wins over team priority; the default is normal. Batch drain acquires at
low.
daari keys team-create batch --priority low
daari keys create ide --priority high --team eng
daari keys list # prio column
Concurrency caps¶
--max-in-flight N on keys create / keys team-create sets a per-key or
team in-flight gate (0 = unlimited). keys list shows a mif column;
keys export --json round-trips the cap in metadata. Admin
GET /v1/daari/keys|teams and the web-ui inventory expose the same field.
daari keys team-create fleet --max-in-flight 4
daari keys create agent --max-in-flight 2 --team fleet
daari keys list # mif column
Cache scope¶
The org-shared cache is the default: identical prompts (L0) and similar prompts (L1) are reused across every key. That is the hit-rate win. A team handling confidential material can opt into a boundary instead.
cache_scope is global (default), team, or key, on both a virtual key
and a team. The stricter setting wins (key over team over global), so a
team set to team isolates every member key even when the key itself stays
global. team folds team_id into the L0 exact key, the Redis L0 key, and
the L1 context key. key folds key_id the same way. global leaves those
hashes unchanged, so entries written before the flag stay reachable.
Two keys on the same team with cache_scope: team share entries. A key on
another team does not, including an L1 hit on a merely similar prompt.
Requests with no auth (master key off, no virtual keys) stay global.
X-Daari-No-Cache still skips the cache entirely.
daari keys team-create legal --cache-scope team
daari keys create counsel --team legal
daari keys create solo --cache-scope key
daari keys list
daari keys list prints the effective scope. POST /introspect includes
cache_scope for the token (never the secret).
Key expiry (doctor / Prometheus / webhook)¶
Enforcement-time expiry is a 401 key_expired. For 90-day rotation, also
enable the proactive plane next to budget alerts:
alerts:
key_expiry_webhook_url: https://hooks.example/daari-key-expiry
key_expiry_webhook_secret: secret://env-file/~/.daari/secrets.env#KEY_EXPIRY_WEBHOOK
key_expiry_warn_days: 14
daari doctor warns inside the warn window; scrape exposes
daari_virtual_keys{status=...}; the webhook POSTs event: key_expiry once
per key entering expiring_soon (signed with X-Daari-Signature when the
secret is set). Full recipe: budgets and frontier.
Backup export / import¶
daari keys export --json / daari keys import round-trip key hashes (never
plaintext) plus teams. The document also carries lifecycle and admission
fields so host migration keeps idle hygiene and gate behavior: keys keep
last_used_at, priority, and cache_scope; teams carry cache_scope and
priority too (team-scoped L0/L1 isolation and admission class). Older export
files without those keys import with today's defaults (last_used_at null,
priority=normal, cache_scope=global). Prefer export/import over copying the
SQLite file when moving hosts — see upgrade.
Verify¶
Call /v1/chat/completions with the key; exceed budget and confirm rejection.