This page explains how to define and publish permission sets for custom Lexicons. If you just want to use existing permission scopes in your OAuth flow, see Permission Requests.
Understanding Permission Sets
The OAuth flow uses permission "scopes" which describe the level of access granted with the session. Rather than requesting broad access to a user's account, atproto apps should request granular permissions for specific record types or API endpoints.
The core pattern is straightforward: request access to only the collections your app needs. For example, if your app creates posts and likes using your own Lexicon:
repo:com.example.post repo:com.example.like blob:*/*
This gives your app permission to create/update/delete records in com.example.post and com.example.like collections, plus permission to upload media. The user's authorization dialog will clearly show these specific permissions — rather than the ominous "access to nearly everything" that broad scopes like transition:generic produce.
For cleaner authorization UIs, you can bundle related permissions into permission sets with human-readable descriptions — see Permission Set design below.
Quick start
Here's a minimal oauth-client-metadata.json for an app that creates its own record types:
{
"client_id": "https://example.com/oauth-client-metadata.json",
"client_name": "My Example App",
"client_uri": "https://example.com",
"redirect_uris": ["https://example.com/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "atproto repo:com.example.post repo:com.example.like blob:*/*",
"token_endpoint_auth_method": "none",
"dpop_bound_access_tokens": true
}
This requests identity (atproto), write access to two record collections, and media upload. For a complete setup guide, see About OAuth.
Minimal permissions
The absolute minimum for an app using atproto OAuth (identity-only, no data access):
atproto
If you also need to request Bluesky profile data:
rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview
If you need to upload images or videos:
blob:*/*
While coarse-grained "transition scopes" like transition:generic are still supported, we encourage adopting granular permissions. For an overview of transition scopes, see Authorization Scopes. For the full granular permission spec, see Permissions.
Permission Set design
Full-featured client apps require a large number of granular permissions to function: dozens or even hundreds of individual permissions. To simplify permission management, Lexicon designers can define "sets" of permissions as part of the schemas they publish. These permission sets are themselves Lexicon schemas and are referred to by namespace ID, such as com.example.authBasicFeatures.
For example, your application could request a permission set like include:com.example.authBasicFeatures?aud=did:web:api.example.com%23svc_appview. This would resolve to a Lexicon that defines a set of permissions required for the app to function:
{
"lexicon": 1,
"id": "com.example.authBasicFeatures",
"defs": {
"main": {
"type": "permission-set",
"title": "Basic App Functionality",
"detail": "Creation of posts and interactions",
"permissions": [
{
"type": "permission",
"resource": "repo",
"collection": ["app.example.post"]
},
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.example.getFeed"
"app.example.getProfile",
...
]
},
]
}
}
}
Note the auth prefix in the app.example.feed.authOnlyPost and com.example.authBasicFeatures examples. This is a naming convention to indicate that the Lexicon is designed for use as a permission set.
Permission sets are limited to expressing permissions that reference resources under the same NSID namespace as the set itself. For example, the set app.example.feed.authOnlyPost could include permissions to app.example.feed.post records and making app.example.feed.getPostThread API endpoint requests to remote services. But it could not grant permissions to app.example.actor.profile. A permission set app.example.authFull, which is a level up in the hierarchy, could include permissions to all these resources.
The title and details fields are descriptive, and will be displayed as part of the OAuth flow. Internationalization of these descriptive fields is supported — refer to the permission sets spec.
The use of inheritAud in the rpc permission allows you to control inheritance behavior.
The blob:*/* permission for image/video handling cannot be included in permission sets and must always be requested separately.
Permission sets are Lexicons too, and are published using the same tooling. See Publishing Lexicons for instructions on using goat to create, lint, and publish your Lexicons.
Progressive scope requests
Your app doesn't need to request all permissions at sign-in. You can start with minimal scopes and request more later as users opt into features. For implementation patterns including dynamic scope selection and upgrading existing sessions, see OAuth Patterns.