# 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

```bash
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

```typescript
// 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](/content/docs/delegation/index.html) [Key & Token Security](/content/docs/security/index.html)
