Documentation
Use Cases

Pre-approved permission tokens

When you know exactly which APIs your agent needs, skip the back-and-forth. Create a scoped permission with predefined scopes and get instant access after one user approval.

When to use scoped permissions

Scoped permissions are ideal when your agent's API needs are known upfront and won't change:

Predictable workflows
Your agent always reads GitHub repos and sends Slack messages — nothing more.
Security-sensitive contexts
You want to guarantee an agent can never request additional scopes beyond what was approved.
Production deployments
Locked-down access for production bots where scope creep is unacceptable.
Compliance requirements
Audit-friendly: the approved scopes are exactly what the permission allows, forever.

Scoped vs. Wildcard

ScopedWildcard
Scopes definedUpfront, at creationDynamically, as needed
Scope changesLocked after approvalCan grow over time
User interactionOne approval, then doneApprove each new scope
Missing scopeHard 403, no recourse403 with approval URL
Best forProduction, complianceExploration, dev agents

Create a scoped permission

terminal
$ keychains permissions create \
--name "github-bot" \
--scopes "github::repo,github::read:user" \
--allow-delegation
> Permission created: pr_scoped_f4e5
> Mode: scoped
> Requested scopes: github::repo, github::read:user
> Status: pending — waiting for user approval
> Approval URL: https://keychains.dev/approve/pr_scoped_f4e5
Note: With scoped permissions, the user sees all requested scopes at once and can approve a subset. After approval, the set is locked — your agent cannot request additional scopes.

Using from code

agent.ts
import { createPermissionRequest, proxyFetch } from '@keychains.dev/machine-sdk';
// Create with explicit scopes
const { permission, approvalUrl } = await createPermissionRequest({
name: 'github-bot',
requestedScopes: ['github::repo', 'github::read:user'],
});
// Send approvalUrl to the user
// After approval, all calls within scope just work:
const repos = await proxyFetch('https://api.github.com/user/repos', {
permissionRequestId: permission.id
});
Tip: If your agent tries to call an API that requires a scope not in its approved set, it gets a hard 403 — no approval URL, no retry. This is by design for security.