Bring Your Own Key (BYOK)
The Operations Assistant runs entirely on your LLM provider key. AuditTrail does not proxy your chat through a hosted endpoint — it stores the key (encrypted at rest), looks it up on each chat request, and makes the provider call directly from your backend. This keeps data, costs, and rate-limit quotas under your control.
Warning — Key storage is per-instance
Keys are encrypted with a derivation of your instance's
AUDITTRAIL_SECRET_KEY. RotatingAUDITTRAIL_SECRET_KEYinvalidates every stored key — you will need to re-add them. Plan rotations accordingly.
Supported providers
| Provider | Storage label | Notes |
|---|---|---|
| OpenAI | openai | All models exposed at api.openai.com/v1 |
| Anthropic | anthropic | Claude 3 / 3.5 / 4 family |
| Gateway | gateway | Routes through AuditTrail's own gateway proxy at /api/v1/gateway/proxy/v1 — useful if you already centralise provider creds |
A user can have multiple keys (one per (provider, label) tuple).
The chat endpoint resolves the active key by (provider, label) with
a fallback to "most-recently-used key on this user" when no provider
is specified.
How the encryption works
- Each tenant's key is encrypted with Fernet using a per-domain
derived key (HKDF-SHA256 over the instance
AUDITTRAIL_SECRET_KEY, salted with the provider domain). - Plaintext is NEVER written to disk. The
fernet_ciphertextcolumn stores the wrapped blob; decryption happens in memory inside the chat stream handler. last4is stored separately so the UI can show a recognisable preview (sk-…abc1) without touching the ciphertext.
UI — /settings/ai-assistant
- Add key — paste a provider key + choose label. The server round-trips a cheap completion to verify the key before saving.
- Test key — re-verify after a rotation.
- Revoke — deletes the ciphertext row + 4-digit preview.
- Last used — timestamp so you can prune stale keys.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/assistant/keys | List keys (last4 only, never ciphertext) |
POST | /api/v1/assistant/keys | Create / rotate a key |
DELETE | /api/v1/assistant/keys/{id} | Revoke |
POST | /api/v1/assistant/keys/{id}/test | Round-trip with the provider |
All user-scoped.
When no key is configured
The chat endpoint returns 412 Precondition Failed with a
structured {"requires_provider_key": true, "settings_url": "/settings/ai-assistant"}
body. The dashboard surfaces this as a configure-CTA rather than a
mock reply. Per the "no mocks, no shortcuts" rule, AuditTrail never
falls back to a pretend response — the UI is always honest about
whether it's wired to a real provider.
Rotating
Rotate by adding a new key with a different label, switching the
assistant to it (POST /chat with label: "rotated"), then
revoking the old row. Zero-downtime.
Audit trail
Every key mutation (create / rotate / revoke) is recorded in the
audit_log table with actor id, resource id, and request IP.