Permission Requests

For app developers

This guide describes how to request permissions for your AT Proto application. OAuth is used to authorize third-party applications to access users' social graphs, and is a prerequisite for the concepts described here.

Control over AT Proto resources is described and granted using permissions. A group of a permissions related to a specific Lexicon namespace (record types and API endpoints) can be bundled together as a "permission set". Both are used in the context of OAuth to grant client software access to account resources on a PDS: For example, the ability to write records of specific types to the user's public repository, or make authenticated API requests to remote services. Developers declare the permissions their app requires to function, and end users are shown the permissions when granting access to the app.

In general, it is best to use existing permission sets for cleaner UI, but you can also request individual permissions and define your own permission sets as needed. Refer to the for users section for how these requests appear in the OAuth flow.

Requesting permissions

When your app initiates an OAuth flow with an ATProto identity provider, it can request specific permission sets by including them in the scope parameter of the authorization request. This should be supplied as a space-separated list in the oauth-client-metadata.json file when registering your OAuth client:

{
scope: "atproto rpc:app.bsky.feed.searchPosts?aud=* blob:*/*",
...
}

OAuth in AT uses Pushed Authorization Requests (PAR). The client metadata file will be fetched dynamically during the session lifecycle, and the scope parameter in oauth-client-metadata.json needs to match the actual authorization HTTP request made by your app:

https://your-authorization-server.com/authorize?
    client_id=1234567890&
    redirect_uri=https://your-app/callback&
    scope=atproto rpc:app.bsky.feed.searchPosts?aud=* blob:*/*
    response_type=code&
    audience=https://myapi&
    state=1234567890

You should try to only request enough permissions to cover the features your app needs. Transition scopes appear to grant permissions to create, update, or delete any of a user's AT records, including posts from other applications. In general, these broad permission requests should be avoided. This helps build trust with your users, and minimizes friction during the authorization process.

From a user experience perspective, it's best to keep the number of requested permission sets low. Each additional permission set adds more information to the authorization dialog, which can lead to decision fatigue. It is possible to allow users to grant only a subset of requested permissions, but this can lead to edge cases.

Permission types

Permissions relate to user owned resources on PDS instances are are represented by five types:

  • repo: Public Repository (records and collections)
  • rpc: Service Authentication (API calls to external services)
  • blob: uploaded media files
  • identity: DID and handle
  • account: hosting status, email address

Wildcards (*) are allowed in scope string syntax, and grants access to all records — the most common example being blob:*/*, for working with images and video. Partial wildcards are not supported — for example, app.bsky.* is not a valid scope string. If you wanted to request multiple permissions from a particular namespace or Lexicon, you would need to list them out individually, or use a permission set. This is intentional, to avoid overbroad permission requests.

Permission sets are themselves Lexicon schemas and are namespaced like other Lexicons. Unlike other Lexicons, permission sets are prefixed with include: in the scope string, and are dynamic; they can be updated.

Rolling out changes

When you update your app to use new permission sets, it's important to coordinate the software release with the permission-set updates. If you add new permission sets that require user approval, users will need to re-authorize your app to grant these new permissions.

Your release and deployment process for the client metadata document may be separate from your app's code deployment process. Make sure to plan accordingly — an updated oauth-client-metadata.json that requests new permissions must be live before a version of your app that requires those permissions.

For Lexicon designers

Lexicons are authored in JSON — you can find examples in the Lexicon Guide. Atmosphere HTTP API methods each scope and implement a particular set of Lexicons, so Lexicons must include valid query parameters, request bodies, and response bodies as appropriate. Each Lexicon, in turn, becomes a permission scope — in other words, app.bsky.feed.post is an AT record format, a web endpoint, and a granular permission all at once.

Lexicon schemas are published publicly as records in ATProto Lexicon repositories. Refer to Lexicon Publication and Resolution for technical details.

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; see the for users section for an example. 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.

Linting and validating

You can use goat to create and publish Lexicons. Before publishing, goat can also be used to lint and validate your Lexicon. For example, pulling and linting app.bsky.feed.post:

$ goat lex pull app.bsky.feed.post
 🟢 app.bsky.feed.post

$ goat lex lint lexicons/app/bsky/feed/post.json
 🟡 lexicons/app/bsky/feed/post.json
    [unlimited-string]: no max length
    [unlimited-string]: no max length
error: linting issues detected

Nobody's perfect 😊

You can create a new Lexicon record with goat lex new record:

$ goat lex new record dev.project.thing

Develop your Lexicon following the style guide, and then eventually publish it with goat lex publish:

$ goat lex publish
 🟢 dev.project.thing

After publishing a new Lexicon, you can verify that it is live with goat lex resolve:

$ goat lex resolve app.bsky.feed.post

{
  "$type": "com.atproto.lexicon.schema",
  "defs": {
    "entity": {
      "description": "Deprecated: use facets instead.",
      "properties": {
        "index": {
          "ref": "#textSlice",
          "type": "ref"
        },
        "type": {
          "description": "Expected values are 'mention' and 'link'.",
          "type": "string"
        },
        "value": {
          "type": "string"
...

Revising Permission Sets

Permission sets can be revised, but removing them or making destructive changes can break existing applications. In general, permission sets should be closely tied to the applications they are designed for, but not necessarily 1:1. Remember that AT Proto applications can utilize multiple different Lexicons.

OAuth flow examples

Apps that are using ATProto identity providers solely for authentication only need to request the atproto permission. This provides the same OAuth flow that you might expect from other identity providers; no features of the Atmosphere social graph are used, and no additional permissions are required:

This is a fully usable Atmosphere integration, and many applications will use this as a starting point. Note that it produces a straightforward, readable "Authorize" flow. The URL of the requesting app is clearly visible, and the permissions dialog states that the app "wants to uniquely identify you," but nothing else.

Next, consider a more complex integration, that requests additional permissions. By default, these will be presented in a summarized view:

In this image, note that there are two different Lexicon groupings: Bluesky permissions, and Skyblur permissions. Each Lexicon defines its own permission sets, as well as its own grouping and presentation of those permissions.

You can click through on the ? icons to see the details of each permission set:

From the expanded ATProto and Bluesky permissions, you might recognize atproto (for OAuth login), blob:*/* (for image and video handling), and several other app.bsky.* permissions for profile and social graph interactions.

The Skyblur Lexicon permissions are defined and summarized differently:

This is all at the discretion of app developers and Lexicon designers. We provide a model for using different Lexicons side by side, and for each Lexicon to define its own permission sets and presentation.