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

Scoped Wildcard
Scopes defined Upfront, at creation Dynamically, as needed
Scope changes Locked after approval Can grow over time
User interaction One approval, then done Approve each new scope
Missing scope Hard 403, no recourse 403 with approval URL
Best for Production, compliance Exploration, dev agents

Create a scoped permission

the 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.

Delegation Key & Token Security