Permissions, Roles & Audiences
Scopes, actor roles, account audiences, and deny-by-default
Three separate ideas, and they are not interchangeable.
Scopes are capability grants. They appear on a manifest (what the plugin asks for) and on a Developer API key (what your tooling may do). The V1 allowlist is locked:
api.read,api.write.limitedapps.read,apps.writeplugins.read,plugins.writekeys.read,keys.writesandbox.runmarketplace.read,marketplace.writecatalog.readconfig.read,config.writewebhooks.declareobservability.publishworkflow.actionevents.subscribe
Some scopes are permanently refused in V1 and will never be granted: finance.settle, finance.write, trust.override, trust.write, pii.export, pii.raw, and api.write.unrestricted. Asking for one produces a specific "forbidden" diagnostic rather than "unknown scope", because the two mean different things: one will never work, the other might be a typo. An API key carries at most 32 scopes.
Roles are the actor identities a plugin may act as. Audiences are the account audiences that may see and use it. Both draw from the same set — ADMIN, OWNER, OPERATOR, DEVELOPER, BUYER, PUBLISHER, VIEWER — and each is capped at 7 entries.
The distinction matters at validation time. A hook is available to specific audiences, so declaring call.completed with no overlapping audience is rejected as HOOK_AUDIENCE_DENIED. An extension point is allowed for specific roles, so ui.admin_nav — restricted to ADMIN, OWNER, and OPERATOR — is rejected for a plugin whose roles and audiences do not include one of them.
Every hook also carries required scopes that your manifest must declare independently. call.completed requires api.read and events.subscribe; declaring the hook without both is HOOK_SCOPE_MISSING. Declaring the scope without the hook is legal but useless.
Deny-by-default is not advisory. Unknown APIs, functions, scopes, hooks, extension points, roles, and audiences all fail closed at the host, at every layer, including inside a test run. Authorization is never relaxed for a harness attempt or a sandbox run.
Developer API keys are show-once plaintext in arvexa_<prefix>_<secret> form. The host stores a hash; the plaintext is displayed once, at issue or rotate, and is never recoverable afterwards — not from the API, not from the CLI, not from arvexa whoami, which prints only the masked form. Revoked and expired keys fail immediately.
The CLI never writes a key anywhere. It has no login command, no credential file, and deliberately no --api-key flag — a value on a command line is readable by other processes through /proc and lands in shell history. Supply a key one of three ways:
--api-key-stdin, piped from a secret manager, so nothing touches disk- the
ARVEXA_DEVELOPER_API_KEYenvironment variable, the standard CI pattern - an
apiKeyEnvreference inarvexa.config.json, which names a variable rather than holding a value
A plaintext apiKey (or token, secret, password, authorization) in the config file is refused with an error naming the alternatives, not accepted with a warning. Every line the CLI writes to a terminal or a log is passed through redaction first, including --json output and error messages, so a key echoed back by a host cannot survive to a CI log.