Apps and keys
The problem. Several tools share one OpenCode server: a Discord bot, n8n, a coding agent. If they share one key, you cannot tell their traffic apart, and you cannot cut one off without cutting all.
Give each tool its own key
"keys": {
"discord-bot": "{env:PLUGBOARD_KEY_DISCORD}",
"n8n": "{env:PLUGBOARD_KEY_N8N}",
"open-webui": "{env:PLUGBOARD_KEY_WEBUI}"
}Each key name is an app. A request made with the n8n key is the app n8n. Callers send the key as Authorization: Bearer <key> (or x-api-key on the Anthropic routes). They never need the OpenCode server password.
What happens:
- Every request is filed under its app: history, usage, limits and metrics are all per app.
- Removing a key, or changing its value, takes effect on the running server at once. Adding a key needs an OpenCode restart.
- Without
keys, the one app isdefault, and only local callers can reach it. On a shared machine, set"requireKeys": trueso the server refuses to run without keys. - App names match
[a-z0-9-]+. Keep keys out of the file with{env:VAR}.
Set up the first app in one command
bunx opencode-plugboard initIt adds plugboard to ~/.config/opencode/opencode.jsonc, pinned to the version that ran. The entry has one app key (generated, 32 random bytes) and "models": "all", which allows every enabled model of every connected provider. The key is printed once, with a curl that lists your models. Restart OpenCode and touch the server, as in Install.
"options": { "keys": { "my-app": "pb_..." }, "models": "all" }Options:
--app <name>names the app (defaultmy-app).--fast <model>adds the aliasfast, for example--fast anthropic/claude-haiku-4-5.--config <path>edits another file.--dry-runprints the change as a diff and writes nothing.--forcereplaces an existing plugboard entry, and gives it a new key. Without it,initstops and leaves the file as it is.
The file is edited as JSONC, so comments, formatting and other settings stay. A file that is not valid JSONC is left untouched. The key is written into the file, so keep the file private, or move the key into the environment with {env:VAR} afterwards.
Fix what an app may do
apps lets the server decide an app's behaviour, so you do not have to trust the app with it.
"aliases": { "fast": "anthropic/claude-haiku-4-5" },
"apps": {
"discord-bot": { "allowedModels": ["fast"], "pinnedModel": "fast", "system": "Answer in two sentences.", "tools": false, "maxOutputTokens": 400 },
"coding": { "allowedModels": ["anthropic/*"], "maxTools": 16 }
}The discord-bot app always runs on the alias fast, whatever model the bot sends. A pinnedModel that names an alias the config does not define stops the server at load, with an error that says so. It gets the system prompt added, has its tools removed, and its answers stop at 400 output tokens. The request still succeeds. The response says what changed:
x-plugboard-policy: tools-strippedAn app with a policy still needs its key in keys (or apps.<name>.key).
Lock a key to some endpoints
"apps": { "claude-code": { "endpoints": ["messages"] } }A call to any other endpoint family answers 403 endpoint_forbidden.
Add and revoke keys without a restart
Set adminKey. It is not a model key: it reads usage for every app and manages runtime keys.
curl -H "Authorization: Bearer $ADMIN_KEY" -H "content-type: application/json" \
-d '{"app":"bot"}' http://127.0.0.1:4010/plugboard/admin/keysThe new key is shown once. The server keeps only a digest.
Split one app by end user
A bot that serves many people is one app. Set userHeader on it and a proxy header such as x-user-id splits history and usage per user. Without it, plugboard reads the request's user field.
Related options
All in the configuration reference:
keys,adminKey,requireKeys- Per-app policy:
allowedModels,pinnedModel,system,tools,maxTools,maxChoices,maxOutputTokens,sampling,requestTimeoutMs,settleMs - Endpoint names
- Server tools (
opencodeTools): let an app's key run OpenCode tools. Read the security warning first. - Admin keys and which key opens which route
- End users