BYOK Credentials
Opbox uses bring-your-own-key (BYOK) for AI credentials. Every server-side AI call - chat, agent worker, workflow AI step, extraction, doc-gen detection - resolves a key through a four-tier chain in priority order.
Five providers are supported: Anthropic (Claude), OpenAI (GPT/o-series), Groq, OpenClaw (a long-lived stateful AI runtime exposed as an OpenAI-compatible gateway with per-agent routing - see OpenClaw Provider), and Hermes (per-tenant onyx-prod runtime exposed as an OpenAI-compatible gateway - see Hermes Provider).
OpenClaw takes precedence. Once OpenClaw is fully configured (bearer + endpoint + agent ID at any tier), the resolver returns it as the active provider regardless of
chatProvider. To opt out, pinchatProvidertoanthropicoropenaiexplicitly. This is deliberate - configuring OpenClaw is itself a deliberate org-level act, so it's implicitly active.
The Four Tiers
| # | Tier | Model | Scope | Sets Cost Source |
|---|---|---|---|---|
| 1 | Personal | UserAiConfig | One user, one workspace | USER_KEY |
| 2 | Workspace | WorkspaceAiConfig | All users in one workspace | WORKSPACE_KEY |
| 3 | Organisation | OrganizationAiConfig | All workspaces in one org | ORG_KEY |
| 4 | Server env | ANTHROPIC_API_KEY, OPENAI_API_KEY | Server-level fallback | SERVER_KEY |
Resolution rule: walk top to bottom, first non-empty key wins, per field. The tier that supplied the key is recorded as keySource and flows into the cost ledger so spend can be attributed.
Field-level inheritance is the non-obvious bit. Filling the Anthropic key on Personal but leaving OpenAI fields blank means only Anthropic calls use your personal key - your OpenAI calls still draw from a lower tier.
Where to Configure
Settings > AI is a tabbed page. Tabs are ordered by resolver priority, left = highest:
| Tab | Visible to | Saves to |
|---|---|---|
| Personal | Every member | UserAiConfig (you, this workspace) |
| Workspace | OWNER / ADMIN | WorkspaceAiConfig (this workspace) |
| Organisation | OWNER / ADMIN | OrganizationAiConfig (this org) |
Members see only the Personal tab. Admins/owners see all three plus the admin-only Doc Gen / Cost Control / Activity Timeline / Tool Catalogue sections below.
Configurable Fields per Tier
| Field | Purpose |
|---|---|
anthropicApiKey | Claude models (chat, workflow steps, extraction, doc-gen detection) |
anthropicModel | Default Claude model. Blank uses the resolver default (currently claude-sonnet-4-5-20250929). |
openaiApiKey | GPT / o-series models + Whisper transcription API |
openaiModel | Default GPT model. Blank uses the resolver default (gpt-4o). |
groqApiKey | Groq-hosted chat/transcription models. |
groqModel | Default Groq model. Blank uses the resolver default. |
hermesBearerToken | Tenant Hermes API bearer. AES-256-GCM encrypted at rest. |
hermesEndpointUrl | Tenant base URL, for example https://gateway.opbox.app/tenant/classical-visas. Opbox appends /v1/chat/completions. |
chatProvider | auto / anthropic / openai / groq / hermes / openclaw. Explicit selections fall back if the key isn't available. |
systemPrompt | Custom instructions prepended to the assistant's built-in system prompt (max 2000 chars on UI; 8000 on API). |
responseDetail | concise / standard / detailed - controls assistant verbosity. |
monthlyTokenCap | Informational soft ceiling. Hard enforcement lives in Cost Control. |
All API keys are AES-256-GCM encrypted at rest and masked (****last4) on reads.
Storage & Encryption
- Key ciphertext is stored directly in the database column.
- The encryption secret is held on the server, never exposed to the client.
- Key rotation: enter a new value in the field; the old ciphertext is overwritten.
keyVerifiedAtis reset; the nightlyverify-ai-keyscron re-probes. - A masked summary (
****abcd) is shown in the UI. The full plaintext is never returned to the client.
Key Verification
A nightly cron (/api/cron/verify-ai-keys) probes every configured key against /v1/models?limit=1 (the cheapest authenticated provider endpoint). Outcome:
| Probe Result | Action |
|---|---|
| 200 OK | keyVerifiedAt = now() |
| 401 / 403 | keyVerifiedAt = null (re-enter to re-verify) |
| Network error / 5xx | No change (transient) |
The Settings page renders a green "Verified DD-MM-YYYY" chip when valid and a "Key rejected by provider - re-enter to re-verify" warning when the verification stamp is null but a key is configured.
Org Policy: Disable Personal Keys
Orgs that need cost control, compliance gates, or strict audit attribution can disable the personal tier entirely.
OrganizationAiConfig.allowPersonalKeys is a boolean (default true). Toggle on the Organisation tab in Settings > AI:
Allow members to override with personal AI keys When off, the resolver skips the user tier and forces every AI call onto workspace -> organisation -> server credentials. Useful for cost-control, compliance, and audit-attribution. Existing personal keys are preserved (read-only) so toggling back on restores each member's setup.
What happens when you turn it off:
| Behaviour | Result |
|---|---|
| Resolver | Skips the user tier entirely - even if UserAiConfig rows exist. |
| Existing rows | Preserved on disk. Toggle back on and they take effect again. |
| Personal page | Renders read-only with a "Personal keys are disabled by your organisation" banner. |
Personal API (PATCH /api/settings/ai-config/personal) | Rejects with 403 PERSONAL_KEYS_DISABLED (defense-in-depth at the write boundary). |
| Cost ledger | New calls bill the next-tier-down key source. Historical USER_KEY rows remain. |
When to use it:
- Cost control - all spend must flow through org-billed keys for chargeback/budgeting.
- Compliance - the org has a vetted enterprise contract with DPA + zero-retention; personal keys would have unknown terms.
- Audit attribution - "all our AI usage went through these audited keys" must be true at the policy level, not the observability level.
API: Tier Endpoints
Every tier has its own endpoint. CSRF-protected on writes. Schema-validated on input. Audit-logged on all writes.
| Tier | Endpoint | Methods | Required Role |
|---|---|---|---|
| Personal | /api/settings/ai-config/personal | GET / PATCH / DELETE | Member |
| Workspace | /api/settings/ai-config | GET / PATCH / DELETE | OWNER / ADMIN |
| Organisation | /api/organizations/[orgId]/ai-config | GET / PUT / DELETE | OWNER / ADMIN |
Every endpoint returns:
config- the masked tier row (ornullif not configured)summary- quick-glance booleans (anthropicConfigured,openaiConfigured, model + chatProvider, etc.)allowPersonalKeys(Personal + Organisation only) - the active org policy
Live Model Catalogue
/api/ai/models proxies the provider's /v1/models endpoint live, using the stored BYOK key. Five-minute in-memory cache keyed by provider:sha256(key). Falls back to a curated whitelist if the live call fails or no key is configured.
This is what powers the model dropdowns in Settings > AI. Means you can pick any model your account has access to, including ones released after the last Opbox deploy.
Resolver Output
For any given workspace and (optional) user, the resolver returns:
provider- the chosen provider (anthropic,openai,groq,hermes, oropenclaw)apiKey- plaintext key (server-only, never sent to the client)model- the default model for that provider (e.g.claude-sonnet-4-5-20250929)systemPrompt- custom org/workspace/user prompt prefix, or nullresponseDetail-concise/standard/detailed, or nullmonthlyTokenCap- informational soft ceiling, or nullchatProvider-auto,anthropic,openai,groq,hermes, oropenclawkeySource- which tier supplied the key (user/workspace/org/server)
Provider-specific lookups (used for transcription, OCR, embeddings, integrations) bypass chatProvider and return null if that provider isn't configured at any tier.
Cross-Tenant Defence
When authorityWorkspaceId differs from workspaceId, the resolver runs a second authorisation check on top of the MCP router's primary check:
- Authority must hold an ACTIVE oversight relationship over the target, OR
- Authority's org must be a super-org managing the target's org.
A bug in the MCP router would otherwise silently let an overseer key spend the target's credentials. This is the second wall.
See Also
- Cost Control - how the ledger uses
keySourcefor attribution. - Hermes Provider - tenant API server routing and provisioning.
- Security & Limits - injection scanning, rate limits, autonomy.
- Agent Worker - how the autonomous worker resolves credentials.