Skip to main content
Every API key carries a list of scopes, and every endpoint declares the scope it requires. A request succeeds when the key holds that scope. There is no implicit access: a key with no scopes can call Whoami and nothing else. Set scopes when you create a key, in the dashboard or through the API Keys endpoints. Dashboard sessions use the same scope vocabulary, resolved from your role’s permissions, so a rule you read here applies to humans and keys alike.

How scopes work

A scope is a resource:verb string, like agents:read or secrets:manage.
  • manage implies read. A key with agents:manage calls every agent endpoint, read or write. Stored scope sets are normalized: agents:read is dropped when agents:manage is present, and duplicates collapse.
  • * is full access. It is the only way to hold everything, including scope families added in the future. A set that contains * normalizes to * alone.
  • Two families are manage-only. Reads of secrets and account are as sensitive as writes, so no :read verb exists for them.

Scope reference

One endpoint is scope-free: Whoami answers for any valid credential, so a key can always identify itself.

Deny by default

A key’s scope set is the whole story. There is no grandfathering and no fallback.
  • A key with an empty scope set is a dead key: it authenticates, it can call Whoami, and every other endpoint denies it.
  • A missing scope denies the request even for reads. agents:manage does not help you list tool sets.
  • Full access is always an explicit grant: * on the key, or every scope spelled out (see the next section).

Managing keys with keys

API keys can mint other API keys, so the API enforces one rule to stop privilege escalation: a key can never produce access it does not hold.
  • Downscope only. Creating or updating a key with scopes the caller does not hold fails with PermissionDenied. Updates check this rule only when the scope set changes, so a narrow key can still rename a broader one.
  • Rotation requires dominance. Rotating a key returns that key’s fresh token, which is equivalent to minting it. The caller’s scopes must cover the target’s.
  • Granting * requires full access. Holding *, or holding the manage verb of every scope family, qualifies. Anything less does not, even if it covers most of the vocabulary.
  • Granting nothing is always allowed. Any caller can mint a dead key.

Disabled keys

An API key is either enabled or disabled. Disable a key with POST /v1/workspaces/{workspaceId}/api_keys/{id}:disable and its token stops authenticating on every endpoint, Whoami included, until you enable it again. The key, its scopes, and its token survive the round trip, which makes disable the right kill switch when you suspect a leak but are not ready to rotate.

Reading a denial

Scope denials return PermissionDenied with a google.rpc.ErrorInfo detail you can branch on. The domain is always api.cadenya.com. granted_scopes echoes the caller’s own scopes back as a comma-separated list. It reveals nothing the caller does not already hold, and it turns “why is this 403ing” into a one-glance diff:

Scopes gate endpoints, not embedded fields

A scope controls which endpoints a key can call. It does not filter the responses of endpoints the key can call. When a response embeds a summary of a related resource (an agent’s info.createdBy profile, a tool set’s tool counts), that summary hydrates whether or not the key holds the related family’s scope. If a key can read the resource, it can read the whole resource.