Skip to main content

Protocol Check-in (Fall 2025)

· 11 min read

We last shared a protocol roadmap back in March 2025, and wow does time fly. If we're being honest, we haven't tied a bow on as many of these threads as we would've hoped. Oh time, strength, cash, and patience!

Fortunately, we have more capacity on the team for protocol work than we did even a couple months ago. Expect to see a lot of work start to land in the coming months.

The Atmosphere is Thriving

Before we dive into what we’re up to, let’s take a moment to celebrate what's happening in the Atmosphere. Our little network is really starting to hit its stride. The energy is incredible and growing by the day! We're seeing new projects pop up constantly, and there's a new level of maturity across the board.

What's really amazing is watching developers help other developers. Development is happening over on Tangled. Devs are sharing updates through Leaflet. Projects like Slices, Microcosm, PDSls, and Graze are making it easier for everyone to build. The AT Protocol Community just announced the second AtmosphereConf this March in Vancouver. This is what decentralized development looks like. Remember: on an open protocol we can just do things.

Big Picture

We’re close to a big milestone for the protocol. Think of it as the “AT 1.0 moment” (even if we don’t literally call it that). As we wrap up our protocol work on auth scopes and Sync1.1, we believe that we’ve fleshed out a complete set of primitives for working with online identities and public broadcast data. This doesn’t mean that development on new features (i.e. private data) isn’t happening. But we think it’s important that we land and mature the work that we’ve already done around public broadcast data before we move on to the next big chunk of work.

With that in mind, our current focus is on adding a layer of maturity and polish to AT to make it the obvious choice when building public sphere social apps.

We’re pursuing this through three main tracks:

  • Developer Experience: Making AT fun and easy to work with. Product-focused devs should be able to build a new social app in a weekend.
  • Governance: Ensuring that AT is something larger and longer-lived than Bluesky
  • Hard Decentralization: Encouraging the emergence of more stakeholders in the network for a more resilient Atmosphere

Developer Experience

We know there are rough edges in the developer experience, but we’ve been hard pressed to find the time to smooth them out while also adding new protocol functionality. With a bit of polish, we’re confident that AT can be fun and easy to build on.

OAuth Cookbooks & Tutorials

OAuth is one of the trickiest parts of building on the protocol — tricky enough that Nick Gerakines is selling out courses on how to do it! OAuth in general is unfortunately complicated in itself, and the decentralized bits of AT only add to that complexity, but that doesn’t mean that it needs to be unapproachable.

We're taking inspiration from Auth0's approach and putting together some comprehensive examples and tutorials that we hope will make getting started with OAuth way easier. Expect to see these published in the next week or two.

We recently wrapped up our dev work on auth scopes and permission sets. Expect an announcement and guides on how to make use of those shortly.

Lexicon SDK

Lexicons are the coordination points for schematic data in the network. As more and more applications are publishing new Lexicons, it’s important that developers can actually make use of them to build native integrations between apps.

The current Lexicon SDK was really a prototype that ended up living on way longer than it probably should have. It doesn’t have to be like this. We’re putting together a new SDK that takes inspiration from Buf’s approach to codegen and schema management. This SDK should make it a breeze to pull in schemas from across the Atmosphere into your application.

Sync Tool

Repository synchronization is at the core of AT. Every app has to do it. And unfortunately it’s difficult to do and even more difficult to do correctly.

We’re continuing to roll out Sync1.1 on the producer (Relay) side, but a fully spec-compliant consumer for it still doesn’t exist. Most developers currently rely on Jetstream to drive their application, but that only helps with live data. There’s no easy way to perform backfill and sync full subsets of the network.

We’re working on a tool that should smooth over the tricky parts of doing sync in AT. This tool will likely take the form of a small service you can run that handles backfill, cutover, cursor management, Sync1.1, and dynamic filtering by DID and collection. That service will do all the tricky stuff and then hand off a nice clean set of record-level operations to your application. It will offer several interfaces including a websocket interface and the ability to translate the firehose into webhooks — meaning AT can work with serverless architectures!

Website

We’re going to be giving atproto.com a facelift in the coming months. You can expect all the work mentioned above to make its appearance there. We’ll also be overhauling the information architecture and publishing new tutorials and guides to make AT more approachable.

Governance

As the Atmosphere matures and more devs are putting time and resources into building companies/projects in the ecosystem, we believe it’s our responsibility to ensure that the protocol has a neutral long-term governance structure around it. The governance of the protocol should outlive Bluesky and be resilient to shifts in the incentive structure that could compromise a future Bluesky PBC.

To that end, we have 3 major developments:

Patent Pledge

We announced our Patent Non-Aggression Pledge at the start of October. Our SDKs and reference implementations are all open source and licensed under permissive software licenses. This patent pledge takes it a step further and establishes additional assurance around patent rights.

Independent PLC Organization

We announced in September that we were working to establish an independent organization to operate the PLC (Public Ledger of Credentials) Directory. PLC is the most common identity method for accounts in the Atmosphere.

Currently this directory is owned and operated by Bluesky PBC. We’re working to establish a Swiss association that will operate the directory and own all assets related to PLC (such as the domain name registration). We’re working with lawyers now to get this done right. Expect an update soon on our progress here.

IETF

We’re hoping to take pieces of AT to the IETF. We've submitted Internet Drafts on the IETF Datatracker and established a mailing list. We're hoping to establish a working group and towards that end, have requested a Birds of a Feather session in Montreal the first week of November. Some folks from the community will be attending and getting together informally. Leave a comment in the community forum if you’ll be around. If you're interested in shaping the future of the protocol at the standards level, we encourage you to get involved!

Hard Decentralization

Hard decentralization refers to the emergence of a resilient and multi-stakeholder Atmosphere that relies less on Bluesky PBC’s existence. There's some overlap with the other two goals here. Improving things like sync make it easier to run alternate infrastructure like Relays and Appviews, and our governance work should help build confidence that the protocol is a genuine public good that’s larger than Bluesky.

To support the goal of hard decentralization, we're also tackling some specific technical challenges.

Improving Non-Bluesky PDS Hosting

The decentralization guarantees of AT come from the locked-open substrate of data hosted by Personal Data Servers (PDSes). One of our current goals to increase the resilience of the network is to encourage more non-Bluesky PDS hosting.

We recently enabled account migration back to bsky.social. We hope this will give users the confidence to experiment with other hosts in the network, knowing they can always migrate back if they need to. Already we’re seeing an uptick in users posting from non-Bluesky PDSes.

Some developers in the network have launched account migration tools that make it easier for non-technical users to migrate between hosts. Examples include PDS MOOver and Tektite. We believe that the next step is to introduce an account migration UI into the PDS itself.

We also intend to make running a PDS more approachable for mid-size hosts. This includes adding auto-scaling rate limits to the Relay reference implementation so that hosts can scale up organically without needing permission or approval. We’re also looking at ways to improve the PDS distribution to make it easier to run and administer with thousands of users.

Technical Improvements to PLC

While we’re working to move PLC out into an independent organization, we’re also planning some technical improvements to PLC to make it more auditable.

Specifically, we want to make it easier to mirror the directory. We intend to introduce a new WebSocket route to the directory that allows new PLC operations to go out in real time. With this, we’ll also publish a PLC Mirror Service reference implementation. This improves both the auditability of PLC and has operational benefits for developers that may wish to run a PLC Mirror closer to their infrastructure.

There are also some legacy non-spec-compliant operations in the directory that make it difficult to write an alternate implementation of PLC that interoperates with the directory. Upon investigation, these have all been traced back to developers probing the PLC system itself, not regular network accounts. We plan to clean those up and harden the directory against similar operations.

This work is building towards the introduction of Transparency Logs (tlogs). Check out Sunlight to see where we’re heading. This probably won’t land in the next six months, but it’s the clear next step for improving trust in PLC.

Alternate Infrastructure

We’re excited to see that more and more devs are experimenting with running alternate infrastructure in the network. Blacksky currently runs a full-network relay (using the rksy-relay implementation!), and is working on a full-network Bluesky appview. Futur showed us all that it was possible with Zeppelin, a full network appview that is now decommissioned. And Red Dwarf is a new Bluesky client that doesn’t use an Appview but rather drives the experience via direct calls to the PDS and generic indices provided by Microcosm.

Please reach out to us if you’re working on running alternate infrastructure. We’re eager to lend a hand.

Private Data

We believe that group-private data is absolutely necessary for the long-term success of the protocol and the Atmosphere. Every day, we wish that we had this figured out and developed already. But as mentioned earlier, we believe that we need to land and mature the existing protocol around public broadcast data before we move on to the next big chunk of work.

We continue to have internal discussions around private data. Paul shared some leaflets that give a sense of the approaches that we’re considering and the rubric by which we’re judging them. The AT Protocol Community is also coordinating a Private Data Working Group to explore some designs for how the system could work.

In the meantime, if you’re building an Atmosphere app, please don’t let the lack of a private data protocol prevent you from building the features that you need to build. Our advice is to publish identities and public data through AT and store any private data that you need on your own server. The semantics of private data will likely look very similar to public data (CRUD operations over a key-value store of typed records, sorted by collection), so if you want to get ahead of the ball, model your data accordingly.

For E2EE DMs, Germ has put together a lovely app built on MLS (Messaging Layer Security) with an AT integration that’s in beta.

Keep up with the Atmosphere

The Atmosphere is getting bigger every day, and it’s starting to get tough to keep up with everything that’s happening! Here are some ways to stay in the loop:

Enabling Account Migration Back to Bluesky’s PDS

· 2 min read

One of the core promises of AT is seamless account migration between PDS hosts. Since federation opened up in the AT network, it has been possible to migrate away from the Bluesky PDS and between non-Bluesky PDSs. However, once you left the Bluesky PDS, returning wasn’t an option.

Today, we’re removing that restriction and allowing returning users to migrate back to the Bluesky PDS. We hope this gives more users the confidence to explore other PDSs in the network, knowing they can return if needed. That being said, account migration remains a potentially destructive operation, so users should be aware of the risks before migrating between hosts.

This does not yet allow users who have never had a bsky.social account to migrate to our PDS. The migration flow works the same as described in the Account Migration documentation, but instead of creating a new account, you’ll log into your existing bsky.social account, import your repo, and reactivate. The Bluesky PDS will automatically handle the diff and index any changes to the repository since you were last active on bsky.social.

We want to see more PDSs in the network and more users on non-Bluesky PDSs. Future work includes an account migration tool hosted at bsky.social, improvements to the PDS distribution - especially for mid-sized deployments, and auto-scaling rate limits at the Relay.

Taking AT to the IETF

· 3 min read

Last week we posted two drafts to the IETF Data Tracker. This is the first major step towards standardizing parts of AT in an effort to establish long-term governance for the protocol.

In particular, we’ve submitted two Internet-Drafts:

Authenticated Transfer Repository and Synchronization: a proposed standard that specifies the AT repository format and sync semantics

Authenticated Transfer: Architecture Overview: an informational draft that goes over the architecture of the broader network and describes how the repository fits into it.

Just today, we were approved for a Birds of a Feather (BOF) session at IETF 124 in Montreal from November 1-7. Details on the BOF can be found Here.

A BOF is a part of the formal IETF process for forming a working group. It involves pulling together interested parties in order to determine if the IETF is a good fit for chartering a working group to work on a particular technology.

This is a “non-working group forming” BOF. The intention is to get feedback on both the charter for the WG and the drafts that we’ve submitted. If things go well, then we’d likely do an interim BOF between IETF 124 and 125 to actually form a working group.

What We’re Planning to Bring (and What We’re Not)

We’re specifically focusing on the repository and sync protocol. We’re not planning to bring Lexicon, AT’s particular OAuth profile, Auth scopes, PLC, the handle system, or other AT components to the IETF right now.

A few reasons for the narrow scope:

  • Working groups need focused charters, especially when bringing a new protocol to the IETF
  • The repo and sync protocol is the most foundational part of AT and is therefore the most impactful to have under neutral governance
  • The repo and sync protocol is the most “IETF-flavored” part of the stack, especially with its reliance on CBOR and WebSockets (both IETF specifications)

If things go well for both sides, we may consider rechartering the working group later. Whether or not a working group forms will not impact how new AT features such as private state are designed or rolled out.

Why IETF?

This is part of an ongoing effort to mature the governance of AT. (See also: the parallel work that we’re pushing forward on moving PLC to an independent organization)

We want AT to have a neutral long-term home, and the IETF seems like a natural fit for several reasons. It’s the home of many internet protocols that you know and use every day: HTTP, TLS, SMTP, OAuth, WebSockets, and many others. The IETF has an open, consensus-driven process that anyone can participate in. And importantly, the IETF cares about both the decentralization of the internet while also keeping it functioning well in practice. This balance between idealism and pragmatism matches how we’ve approached the challenges of building a large-scale decentralized social networking protocol.

What You Can Do

Read the drafts! Take a look at what we’ve submitted and see what you think. We have a public GitHub repo where you can comment on the drafts and provide feedback. We’re hoping to iterate on the drafts at least once before the BOF and already have a few issues noted that we need to address.

If you’re planning to attend IETF 124 in Montreal, let us know! We’d love to connect with folks who are interested in this work.

Creating an Independent Public Ledger of Credentials (PLC) Directory Organization

· 2 min read

The Bluesky Social app is built on an open network protocol that refers to each user by a unique Decentralized Identifier, or DID (a W3C standard). The most popular supported DID method was developed in-house by Bluesky Social, and is called "Public Ledger of Credentials", or PLC. The PLC identity system currently relies on a global directory service to distribute identity updates, and that directory service has been operated by Bluesky as well.

Until now.

As the next step of maturing governance of the PLC identity system, Bluesky Social PBC is supporting the creation of an independent organization to operate the directory. The organization will set policies and rate-limits, hold any related intellectual property, and coordinate future evolution and development of the system. While Bluesky and the AT network are the largest use case for the PLC system today, it is a general-purpose technology, and will be developed and operated as a vendor- and application-neutral public good.

After considering several jurisdictions, legal structures, and potential parent organizations, the new entity will form as a Swiss Association. In a period of international uncertainty around Internet governance, Switzerland provides a credibly neutral and stable global home. This entity will have the independence and flexibility to transform itself into another legal structure as it evolves. Initial board members and other logistical details are still being finalized.

We do not expect this organization to be the final governance structure for PLC, and we do not expect a single global directory to be the final technical architecture for the system. But as the AT network grows and diversifies, it becomes increasingly important for the identity system to have a clear path toward independence. It is also our hope that a more neutral and independent PLC identity system will find use by other projects in the broader open web ecosystem.

You can read more about the PLC identity system at web.plc.directory, and more about its use in the AT network in the Identity Protocol Documentation.

OAuth Improvements

· 7 min read

We've been making improvements to the end-user and developer experiences with atproto OAuth, and wanted to share some updates.

Email Transitional Scope

One of the few gaps between password authentication and the initial ("transitional") set of OAuth scopes was access to an account's email address (and email confirmation status). We have implemented a new OAuth scope, transition:email that clients can request for access to the account's email. The email itself can be accessed via the com.atproto.server.getSession endpoint on the PDS, using an OAuth access token.

Public versus Confidential Clients

The difference between "public" and "confidential" OAuth clients is one of the more complex tradeoffs for application developers. Tradeoffs in security, privacy, trust, application architecture, session lifetime, and user experience are all entangled.

We have a new essay that tries to untangle this knot, explaining the security concerns and trade-offs involved: "OAuth Client Security in the Atmosphere".

We also have a proposal to enable a new architecture: "Client Assertion Backend for Browser-based Applications". In this architecture, in-browser web apps (SPAs) would communicate with a simple and generic backend server to generate client attestations for token requests. The backend could "veto" client sessions (to address security incidents), but would not directly mediate token requests. This would be distinct from the Token Mediating Backend (TMB) architecture. Note that with both architectures, the server can not hijack the session or make "offline" authenticated requests on the user's behalf, assuming the DPoP keys are held securely on-device by the web app.

Session Lifetimes

Many developers are encountering friction with OAuth session lifetimes, especially using in-browser "public" clients. The existing lifetime limitations mean that un-refreshed sessions would time out after just two days.

There will always be shorter session lifetimes for public clients, but we think the session lifetimes can be increased without a significant security trade-off. We are planning to roll out the following session lifetimes:

  • Public Clients
    • increase overall session lifetime (after which tokens can not be refreshed) from one week to two weeks
    • increase the lifetime of individual refresh tokens from two days to two weeks (the same as the overall session lifetime; refreshing tokens does not extend the overall session)
  • Confidential Clients
    • increase the lifetime of individual refresh tokens from one month to three months (if tokens are not refreshed, the session ends before the overall session lifetime limit)
    • increase overall session lifetime (assuming tokens are refreshed) from one year to two years; including existing sessions

Remember that the maximum lifetime of OAuth access tokens is much shorter: the specification recommends a limit of 30 minutes, and the reference implementation uses 15 minutes.

There is a tracking issue here: bluesky-social/atproto#3883

Auth Scopes

Protocol design on Auth Scopes is wrapping up. This functionality will allow app developers to request granular permissions to specific atproto resources, like records (by NSID), remote API endpoints (using service proxying), account hosting status, and identity updates. The system will also allow Lexicon designers to define higher-level "bundles" of permissions for atproto resources in the same NSID namespace, to make end-user permission requests simpler.

You can learn more by reading an earlier draft of this protocol feature from March 2025.

We will begin server-side implementation and integration work on this feature soon after a final proposal is published.

Other Changes and Bug Fixes

The DPoP JWTs created by the @atproto/oauth-client TypeScript package incorrectly included query parameters as part of the htu request URL field, which goes against the DPoP specification. The client package has been fixed as of version 0.3.18.

The Authorization Server used to require a DPoP proof during Pushed Authorization Requests when the parameters contained a dpop_jkt. This was not valid per spec and is thus no longer the case.

The OAuth Client Implementation Guide previously instructed developers to include the Auth Server Issuer (host URL) in DPoP JWTs included on authenticated requests to the PDS, in the iss field. The example Python code and TypeScript OAuth client implementation also included this field. This is not required by the DPoP specification, and should not be implemented by clients or SDKs. We have updated the guide and implementations to remove this field.

The TypeScript OAuth client SDK was incorrectly sending HTTP POST requests using JSON bodies, instead of form-encoding (the server was flexible to either encoding). This has been corrected, and requests that should use form-encoding (like PAR) now do.

A couple of subtle bugs with the TypeScript OAuth client SDK and DPoP nonce caching have been fixed.

If a client app redirects to the reference PDS without a login_hint, and the user was already logged in, the auth flow now proceeds directly to an account selector, instead of displaying a "Create Account or Log In" page. This makes the flow smoother and reduces a click.

The client app redirect URL no longer needs to be on the same host origin as the client metadata document.

Deprecation notice

The following invalid behaviors were detected in our reference implementation. As of now, these are considered as deprecated and might change in the future. Make sure you use the latest version of our SDKs, and if you implemented your own, please check that it is compliant.

  • DPoP proofs that contain a query or fragment in the htu claim should be rejected.

  • JWT for the client attestation using the private_key_jwt authentication method MUST contain an exp claim. This is currently not enforced by the reference implementation.

  • When performing a Pushed Authorization Request, the client must either provide a dpop_jkt authorization request parameter, or provide a DPoP proof header. The reference implementation does not enforce this, allowing the DPoP proof header to be provided only during the token exchange.

Remaining Limitations

The account management interface on the reference PDS implementation allows revoking OAuth client sessions. On a free-standing PDS, this has an immediate effect: both access and refresh tokens stop working immediately. But the Bluesky-hosted PDS instances ("mushroom servers") use an "entryway" service as an OAuth Auth Server. A side-effect of this is that revoking client sessions could take up to 15 minutes to take effect: refresh tokens immediately stop working, but access tokens do not. This is a relatively common design tradeoff in auth systems involving many servers but is not currently well communicated in the interface.

The TypeScript OAuth Authorization server implementation (`@atproto/oauth-provider`) currently does not correctly validate that the initial client attestation signing key is present in the client metadata document over the full lifetime of an OAuth session. Instead, it simply validates each attestation (eg, on token refreshes) against the current JWKs in the client metadata. In other words, if a client removes a public key from the JWK set in the client metadata document, the auth server is supposed to reject future token refresh requests for sessions that started by using that specific key; but the current implementation does not do this. Similarly, our OAuth client implementation did not enforce the use of the same key whenever refreshing sessions. The tracking issue for this is https://github.com/bluesky-social/atproto/pull/3847. Note that fixing this bug might result in sessions becoming invalid if clients do not adapt their implementation to use the same key over the session’s lifetime. Clients only using a single key should not be impacted.

Network Account Management

· 6 min read

Accounts in the atproto network are app-neutral: a single account can be used for short posts, long-form blogging, events, and more. But until now, the best way for users to create and manage accounts has been through client apps themselves.

We recently shipped new functionality to the PDS reference implementation (and Bluesky's hosting service) which provides a web interface to create and manage accounts directly on the PDS itself. This post describes the new functionality, and what this means for independent app developers and service providers.

OAuth Account Sign-Up Flow

The PDS reference implementation has included a web interface for authorization flows since September 2024, which allowed users to login to OAuth client apps. However, users needed to already have an account. One work-around was to have users create an account using the Bluesky app first, but this was a very awkward onboarding flow, and gave Bluesky Social (the app) an outsized role in the ecosystem.

The PDS now allows users to create new accounts during the OAuth flow where client apps can initiate an auth request to a specific PDS instance without having an account identifier for the user. When they are redirected to the PDS (the OAuth "Auth Server"), they are given the option to create a new account. This includes the full account creation flow: selecting a handle, agreeing to any terms of service, and passing any anti-abuse checks. The user is then prompted to complete the client app authorization flow and will be redirected back to the app once they have signed up.

Web interface for "pds.example.com", with buttons "Create a new account", "Sign in", and "Cancel". There is a language-selection drop-down menu in the lower right corner.

Web interface for "pds.example.com", with buttons "Create a new account", "Sign in", and "Cancel". There is a language-selection drop-down menu in the lower right corner.

"Create Account" web interface, prompting user to "Choose a username" as "Step 1 of 2"

"Create Account" web interface, prompting user to "Choose a username" as "Step 1 of 2"

On an independent or self-hosted PDS instance, this process is unbranded and independent of Bluesky Social by default. PDS operators can customize the interface with the PDS_LOGO_URL, PDS_SERVICE_NAME, and other configuration variables. When using Bluesky's hosting service (bsky.social), the flow will indicate that Bluesky (the company) is the hosting provider.

You can experiment with this flow by entering a PDS hostname (or https://bsky.social) using the Python Flask OAuth demo at: https://oauth-flask.demo.bsky.dev/oauth/login

Account Management Interface

The reference implementation's account management interface is at the path /account. This specific path does not need to be a protocol norm or requirement, and we intend to make any account management URL machine-discoverable in the future. For an independent PDS at pds.example.com, the full URL would be https://pds.example.com/account. For an account hosted on Bluesky's hosting service, the URL is https://bsky.social/account.

Web Sign-In interface, asking for "Identifier" and "Password". There is a "Forgot Password?" link, and a language selector drop-down.

Web Sign-In interface, asking for "Identifier" and "Password". There is a "Forgot Password?" link, and a language selector drop-down.

Users will already be signed in if they have done an OAuth approval flow for an app. If not, they should sign in using their full account password (not an app password). Multiple accounts on the same PDS can be signed in at the same time, with an account selector dialog.

Web interface for "Your Account". Shows a list of "Connected apps" (Statusphere React App and OAuth Flask Backend Demo), with "Revoke access" buttons for each. Also has a list of "My Devices", showing "Linux - Firefox" and "Linux - Chrome", the latter indicated as "This device", both with "Sign Out" buttons.

Web interface for "Your Account". Shows a list of "Connected apps" (Statusphere React App and OAuth Flask Backend Demo), with "Revoke access" buttons for each. Also has a list of "My Devices", showing "Linux - Firefox" and "Linux - Chrome", the latter indicated as "This device", both with "Sign Out" buttons.

The account management view currently allows users to view authorized OAuth applications, and a list of devices/browsers which are signed in to the host itself. It does not show password-based auth sessions to apps.

We expect to implement more functionality in this interface in the future. For example, email updates, password changes, and account deactivation, deletion, or migration actions are all application-agnostic and could be implemented here. Additional 2FA methods (such as passkeys, OTP, or hardware dongles) could all be configured through this interface.

The specific design and features for account management are largely left to implementations, and are not specified or required by the protocol. We expect that there will be a common overlap in features provided, but PDS hosting providers are free to innovate and differentiate. Providers might bundle other network services (such as email, calendaring, or web hosting), or implement atproto hosting as a component of existing services (such as blog hosting).

Other OAuth Progress

The protocol team released an initial Auth Scopes design sketch in March, and expects to have a working version in the network before long. In the meanwhile, we are planning to introduce an email-specific transitional OAuth scope, which will let OAuth clients access account email addresses and email verification status.

App developers in the ecosystem have been implementing atproto OAuth clients of both the "public" and "confidential" types. While the "public" client type can be easier to implement, we need to emphasize that long-lived auth sessions (eg, more than a couple days or weeks before re-authentication) are only possible with "confidential" clients. This requires some degree of auth offload to an app server which holds cryptographic private keys and signs token refresh requests. You can read more about this in the "Confidential Client Authentication" section of the atproto OAuth specifications.

As always, we encourage you to check in on both official and community channels for updates on AT Protocol. An increasing share of development is happening out in the ecosystem, with more projects and organizations getting started by the week:

Relay Updates for Sync v1.1

· 5 min read

We have an updated relay implementation which incorporates Sync v1.1 protocol changes, and are preparing to switch over the bsky.network relay operated by Bluesky. This post describes new infrastructure developers can start using today and describes our plan for completing the migration.

Updated Relay Implementation

The new version of the relay is available now in the indigo go git repository, named simply relay. It is a fork and refactor of the bigsky relay implementation, and several components have stuck around. The biggest change is that the relay no longer mirrors full repository data for all accounts in the network: sync v1.1 relays are now "non-archival". We have actually been operating bsky.network with a patched non-archival version of bigsky for some time, but this update includes a number of other changes:

  • the new #sync message type is supported
  • the deprecated #handle, #migration, and #tombstone message types are fully removed (they were replaced with #identity and #account)
  • the sync v1.1 changes to the #commit message schema are supported
  • message validation and data limits updated to align with written specification
  • account hosting status and lifecycle updated to match written specification
  • #commit messages validated with MST inversion (controlled by "lenient mode" flag)
  • new com.atproto.sync.listHosts endpoint to enumerate subscribed PDS instances
  • com.atproto.sync.getRepo endpoint implemented as HTTP redirect to PDS instance
  • simplified configuration, operation, and SQL database schemas

The relay can aggregate firehose messages for the full atproto network on a relatively small and inexpensive server, even with signature validation and MST inversion of every message on the firehose. The relay can handle thousands of messages per second using on the order of 2 vCPU cores, 12 GByte of RAM, and 30 Mbps of inbound and outbound bandwidth. Disk storage depends on the length of the configurable "backfill window." (A 24-hour backfill currently requires a couple hundred GByte of disk.)

Staged Rollout

The end goal is to upgrade the bsky.network relay instance and for all active PDS hosts in the network to emit valid Sync v1.1 messages. We are rolling this out in stages to ensure that no active services in the network break.

We have two new production relay instances:

  • relay1.us-west.bsky.network
  • relay1.us-east.bsky.network

These both run the new relay implementation and attempt to crawl the entire atproto network. The two instances are operationally isolated and subscribe to PDS instances separately, though configuration will be synchronized between them periodically. They have sequence numbers which started at 20000000000 (20 billion) to distinguish them from the current bsky.network relay, which has a sequence around 8400000000 (8.4 billion). Note that the two new relays are independent from each other, have different sequence numbers, and will have different ordering of messages. Clients subscribing to these public hostnames will technically connect to rainbow daemons which fan-out the firehose.

We encourage service operators and protocol developers to experiment with these new instances. They are running in "lenient" MST validation mode and should work with existing PDS instances and implementations. PDS hosts can direct requestCrawl API requests to them. If hosts don't show up in listHosts, try making some repo updates to emit new #commit messages. Bluesky intends to operate both of these relays, at these hostnames, for the foreseeable future.

For the second stage of the roll-out (in the near future), we will update the bsky.network domain name to point at one of these new relay instances. If all goes smoothly, firehose consumers will simply start receiving events with a higher sequence number.

As a final stage, we will re-configure both instances to enforce MST inversion more strictly and drop #commit messages which fail validation. To ensure this does not break active hosts and accounts in the network, the relays currently attempt strict validation on every message and log failures (even if they are passed through on the firehose in "lenient" mode). We will monitor the logs and work with PDS operators who have not upgraded. If necessary, we can "ratchet" validation by host, meaning that new hosts joining the network are required to validate but specific existing hosts are given more time to update.

Other Sync v1.1 Progress

We have a few resources for PDS developers implementing Sync v1.1:

  • the goat CLI tool has several --verify flags on firehose consumer command, which can be pointed at a local PDS instance for debugging
  • likewise the relay implementation can be running locally (eg, using sqlite) and pointed at a local PDS instance
  • interoperability test data in the bluesky-social/atproto-interop-tests git repository, both for MST implementation details, and for commit proofs
  • the written specifications have not been updated yet, but the proposal doc is thorough and will be the basis for updated specs

While not strictly required by the protocol, both of the new relay hostnames support the com.atproto.sync.listReposByCollection endpoint (technically via the collectiondir microservice, not part of the relay itself). This endpoint makes it much more efficient to discover which accounts in the network have records of a given type and is particularly helpful for developers building new Lexicons and working with data independent of the app.bsky.* namespace.

The need for ordered repository CAR file exports has become more clear, and an early implementation was completed for the PDS reference implementation. That implementation is not performant enough to merge yet, and it may be some time before ordered CAR files are a norm in the network. The exact ordering also needs to be described more formally to ensure interoperation. Work has not yet started on the "partial synchronization" variant of getRepo, which will allow fetching a subset of the repository.

2025 Protocol Roadmap (Spring and Summer)

· 6 min read

Join the Github discussion here.

Metamorphosis is the process where a caterpillar forms a chrysalis, liquifies its own body, and emerges as an imago, or butterfly. It is a pretty nifty trick. Anyways, trees are budding, Lexicons are blossoming, spring is happening, and it is time for an update to the AT Protocol roadmap.

We recently summarized progress on the protocol in 2024. This blog post will be forward looking, covering our protocol goals for the next 6-7 months. As a high-level summary:

  • Updates to the relay, firehose, and public repo sync semantics (Sync v1.1) are starting to roll out
  • Design work on Auth Scopes has started, which will improve atproto OAuth
  • PDS will get a web interface for generic account management and signup
  • Shared data (eg, group privacy) will likely be the next major protocol component, with E2EE DMs following that

We also have a quick section on deprecated developer patterns; please give those a look!

Sync v1.1

We are iterating on the core public data synchronization components of the protocol. Relays will become much cheaper to operate, and we’re clarifying the process for fully validating the firehose. The full proposal gets into all the details, but to summarize:

  • Efficient mechanism for validating MST operations in individual repository commits ("inductive firehose")
  • Adding a new #sync message type, and removing the tooBig flag on commits
  • New desynchronized and throttled account statuses, to communicate temporary failures
  • New com.atproto.sync.listReposByCollection endpoint to help with backfill

Auth Scopes

We are updating OAuth for AT Protocol with a way to request and grant granular permissions. For example, it should be possible to give a client permission to read and write posts on Bluesky, but not insert arbitrary block records or access DMs. This is obviously important for user control, privacy, and account security. The system will allow application designers to declare their own auth scopes, as part of the Lexicon system. PDS implementations will be able to enforce these permissions in an interoperable way, at runtime. We will share more details soon.

In addition to completing OAuth for existing apps, Auth Scopes will be necessary for upcoming protocol features, like group-private data and on-protocol DMs.

PDS Account Management

More and more folks are building independent apps on atproto. While they can use OAuth to authenticate users from any PDS instance, account signup is more complicated. In theory it is possible to implement account creation using the com.atproto.* Lexicons, but in practice this is difficult (or impossible) to implement in independent apps, because of anti-bot measures. This results in developers directing new users to sign up with Bluesky, which is a bad user experience, and conflates having an account on the AT Protocol with having a Bluesky account.

To improve this situation, we are implementing a web interface in the PDS reference distribution which will give users a less-branded account sign-up experience. The PDS technically already has a web interface, used for the OAuth authorization flow, and this simply extends that. Over time, we expect the web interface to provide generic account management capabilities, such as password recovery flows, additional 2FA mechanisms, management of active auth sessions, account deactivation, etc.

The details of the web interface will be implementation-specific. Other PDS implementations might provide different functionality, or make different design choices.

Privately Shared Data and E2EE DMs

We believe that robust support for group-private data will be necessary for the long-term success of the protocol (and for apps built on the protocol). Similarly, the ability to share private content with a specific group or audience continues to be a top feature request for both the AT Protocol and the Bluesky app. Just as we’re currently doing with public conversation on the Bluesky app and the AT Protocol, we also want to co-design the protocol specification for private data in tandem with specific real-world product features: this results in better outcomes for both. Designing for privacy is pretty different from designing for global broadcast, and we think the data architecture will probably look pretty different from the MST + firehose system.

Shared data will depend on Auth Scopes, and we don't expect to start design work until that is complete.

Looking forward, we continue to have plans to implement on-protocol DMs and E2EE group chat. However, we don’t expect to start work on this until after shared data is implemented. Meanwhile, there has been exciting progress in the broader tech world around the Messaging Layer Security (MLS) standard, and we are optimistic that we will be able to build on reusable components and design patterns when the time comes. It is also possible (and exciting!) that the atproto dev community will experiment and build E2EE chat apps off-protocol before there is an official specification.

Deprecations

There are a few protocol features and API endpoints which were supported in early days of atproto development. They have been deprecated for some time, but have continued to function. As the protocol stabilizes, we want to ensure developers are building against the current protocol, and will start to remove this functionality more aggressively.

A simple deprecation are the #tombstone, and #handle, and #migrate events on the firehose. These were replaced with #identity and #account early last year (2024), and have been deprecated since then. We will remove them from the atproto Lexicons entirely soon.

Client apps should resolve user login identifiers (handles or DIDs) to PDS instances, and should not hardcode the bsky.social domain for API requests. In the early days, all API requests could be made to this server, and we have continued to proxy requests to avoid breakage. Most clients and SDKs have been updated, and we may stop proxying in the near future.

When making proxied requests to a PDS, clients can specify a remote service to forward to via the atproto-proxy header. To date, the reference PDS implementation has automatically forwarded app.bsky.* endpoints to the Bluesky API server (api.bsky.app). No other services or Lexicon namespaces in the network have this sort of default forwarding. To keep the network more provider-neutral, clients should not rely on this default, and should always specify a service in the proxy header. The service DID reference for the Bluesky AppView is did:web:api.bsky.app#bsky_appview; you can see more example service DIDs in the API Hosts and Auth docs.

Keep up with Ecosystem

The AT Protocol developer ecosystem continues to grow at a fast pace, with more developers launching new projects and organizations by the week. Here are some ways to stay updated or get involved:

@atproto/api v0.14.0 release notes

· 12 min read

Today we are excited to announce the availability of version 0.14 of our TypeScript SDK on npm.

This release is a big step forward, significantly improving the type safety of our @atproto/api package. Let’s take a look at the highlights:

  • Lexicon derived interfaces now have an explicitly defined $type property, allowing proper discrimination of unions.
  • Lexicon derived is* utility methods no longer unsafely type cast their input.
  • Lexicon derived validate* utility methods now return a more precise type.

Context

Atproto is an "open protocol" which means a lot of things. One of these things is that the data structures handled through the protocol are extensible. Lexicons (which is the syntax used to define the schema of the data structures) can be used to describe placeholders where arbitrary data types (defined through third-party Lexicons) can be used.

An example of such a placeholder exists in the Lexicon definition of a Bluesky post (app.bsky.feed.post), which enables posts to have an embed property defined as follows:

  "embed": {
"type": "union",
"refs": [
"app.bsky.embed.images",
"app.bsky.embed.video",
"app.bsky.embed.external",
"app.bsky.embed.record",
"app.bsky.embed.recordWithMedia",
]
}

The type of the embed property is what is called an "open union". It means that the embed field can basically contain anything, although we usually expect it to be one of the known types defined in the refs array of the Lexicon schema (an image, a video, a link, or another post).

Systems consuming Bluesky posts need to be able to determine what type of embed they are dealing with. This is where the $type property comes in. This property allows systems to uniquely determine the Lexicon schema that must be used to interpret the data, and it must be provided everywhere a union is expected. For example, a post with a video would look like this:

{
"text": "Hey, check this out!",
"createdAt": "2021-09-01T12:34:56Z",
"embed": {
"$type": "app.bsky.embed.video",
"video": {
/* reference to the video file, omitted for brevity */
}
}
}

Since embed is an open union, it can be used to store anything. For example, a post with a calendar event embed could look like this:

{
"text": "Hey, check this out!",
"createdAt": "2021-09-01T12:34:56Z",
"embed": {
"$type": "com.example.calendar.event",
"eventName": "Party at my house",
"eventDate": "2021-09-01T12:34:56Z"
}
}
note

Only systems that know about the com.example.calendar.event Lexicon can interpret this data. The official Bluesky app will typically only know about the data types defined in the app.bsky lexicons.

Revamped TypeScript interfaces

In order to facilitate working with the Bluesky API, we provide TypeScript interfaces generated from the lexicons (using a tool called lex-cli). These interfaces are made available through the @atproto/api package.

For historical reasons, these generated types were missing the $type property. The interface for the app.bsky.embed.video, for example, used to look like this:

export interface Main {
video: BlobRef
captions?: Caption[]
alt?: string
aspectRatio?: AppBskyEmbedDefs.AspectRatio
[k: string]: unknown
}

Because the $type property is missing from that interface, developers could write invalid code, without getting an error from TypeScript:

import { AppBskyFeedPost } from '@atproto/api'

const myPost: AppBskyFeedPost.Main = {
text: 'Hey, check this out!',
createdAt: '2021-09-01T12:34:56Z',
embed: {
// Notice how we are missing the `$type` property
// here. TypeScript did not complain about this.

video: {
/* reference to the video file, omitted for brevity */
},
},
}

Similarly, a Bluesky post’s embed property was previously typed like this:

export interface Record {
// ...
embed?:
| AppBskyEmbedImages.Main
| AppBskyEmbedVideo.Main
| AppBskyEmbedExternal.Main
| AppBskyEmbedRecord.Main
| AppBskyEmbedRecordWithMedia.Main
| { $type: string; [k: string]: unknown }
}

It was therefore possible to create a post with a completely invalid "video" embed, and still get no error from the type system:

import { AppBskyFeedPost } from '@atproto/api'

const myPost: AppBskyFeedPost.Main = {
text: 'Hey, check this out!',
createdAt: '2021-09-01T12:34:56Z',

// This is an invalid embed, but TypeScript
// does not complain.
embed: {
$type: 'app.bsky.embed.video',
video: 43,
},
}

We have fixed these issues by making the $type property in the generated interfaces explicit. The app.bsky.embed.video interface now looks like this:

export interface Main {
$type?: 'app.bsky.embed.video'
video: BlobRef
captions?: Caption[]
alt?: string
aspectRatio?: AppBskyEmbedDefs.AspectRatio
}

Notice how the $type property is defined as optional (?:) here. This is due to the fact that the schema definitions are not always used from open unions. In some cases, a particular schema can be referenced from another schema (using a "type": "ref"). In those cases, there will be no ambiguity as to how the data should be interpreted.

For example, a "Bluesky Like" (app.bsky.feed.like) defines the following properties in its schema:

  "properties": {
"createdAt": { "type": "string", "format": "datetime" },
"subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
},

As can be seen, the subject property is defined as a reference to a com.atproto.repo.strongRef object. In this case, there is no ambiguity as to how the subject of a like should be interpreted, and the $type property is not needed.

const like: AppBskyFeedLike.Record = {
$type: 'app.bsky.feed.like',
createdAt: '2021-09-01T12:34:56Z',
subject: {
// No `$type` property needed here
uri: 'at://did:plc:123/app.bsky.feed.post/456',
cid: '[...]',
},
}

Because the $type property of objects is required in some contexts while optional in others, we introduced a new type utility type to make it required when needed. The $Typed utility allows marking an interface’s $type property non-optional in contexts where it is required:

export type $Typed<V> = V & { $type: string }

The embed property of posts is now defined as follows:

export interface Record {
// ...
embed?:
| $Typed<AppBskyEmbedImages.Main>
| $Typed<AppBskyEmbedVideo.Main>
| $Typed<AppBskyEmbedExternal.Main>
| $Typed<AppBskyEmbedRecord.Main>
| $Typed<AppBskyEmbedRecordWithMedia.Main>
| { $type: string }
}

In addition to preventing the creation of invalid data as seen before, this change also allows properly discriminating types when accessing the data. For example, one can now do:

import { AppBskyFeedPost } from '@atproto/api'

// Say we got some random post somehow (typically
// via an API call)
declare const post: AppBskyFeedPost.Main

// And we want to know what kind of embed it contains
const { embed } = post

// We can now use the `$type` property to disambiguate
if (embed?.$type === 'app.bsky.embed.images') {
// The `embed` variable is fully typed as
// `$Typed<AppBskyEmbedImages.Main>` here!
}

$type property in record definitions

While optional in interfaces generated from Lexicon object definitions, the $type property is required in interfaces generated from Lexicon record definitions.

is* utility methods

The example above shows how data can be discriminated based on the $type property. The SDK provides utility methods to perform this kind of discrimination. These methods are named is* and are generated from the lexicons. For example, the app.bsky.embed.images Lexicon used to generate the following isMain utility method:

export interface Main {
images: Image[]
[x: string]: unknown
}

export function isMain(value: unknown): value is Main {
return (
value != null &&
typeof value === 'object' &&
'$type' in value &&
(value.$type === 'app.bsky.embed.images' ||
value.$type === 'app.bsky.embed.images#main')
)
}

That implementation of the discriminator is invalid.

  • First, because a $type is not allowed to end with #main (as per AT Protocol specification).
  • Second, because the isMain function does not actually check the structure of the object, only its $type property.

This invalid behavior could yield runtime errors that could otherwise have been avoided during development:

import { AppBskyEmbedImages } from '@atproto/api'

// Get an invalid embed somehow
const invalidEmbed = {
$type: 'app.bsky.embed.images',
// notice how the `images` property is missing here
}

// This predicate function only checks the value of
// the `$type` property, making the condition "true" here
if (AppBskyEmbedImages.isMain(invalidEmbed)) {
// However, the `images` property is missing here.
// TypeScript does not complain about this, but the
// following line will throw a runtime error:
console.log('First image:', invalidEmbed.images[0])
}

The root of the issue here is that the is* utility methods perform type casting of objects solely based on the value of their $type property. There were basically two ways we could fix this behavior:

  1. Alter the implementation to actually validate the object's structure. This would be a non-breaking change that has a negative impact on performance.
  2. Alter the function signature to describe what the function actually does. This is a breaking change because TypeScript would start (rightfully) returning lots of errors in places where these functions are used.

Because this release introduces other breaking changes, and because adapting our own codebase to this change showed it made more sense, we decided to adopt the latter option.

tip

In many cases where data needs to be discriminated, this change in the signature of the is* function won't actually cause any issues when upgrading the version of the SDK.

For example, this is the case when working with data obtained from the API. Because an API is a "contract" between a server and a client, the data returned by Bluesky's server APIs is "guaranteed" to be valid. In these cases, the is* utility methods provide a convenient way to discriminate between valid values.

import { AppBskyEmbedImages } from '@atproto/api'

// Get a post from the API (the API's contract
// guarantees the validity of the data)
declare const post: AppBskyEmbedImages.Main

// The `is*` utilities are an efficient way to
// discriminate **valid** data based on their `$type`
if (isImages(post.embed)) {
// `post.embed` is fully typed as
// `$Typed<AppBskyEmbedImages.Main>` here!
}

validate* utility methods

As part of this update, the signature of the validate* utility methods was updated to properly describe the type of the value in case of success:

import { AppBskyEmbedImages } from '@atproto/api'

// Aliased for clarity
const Images = AppBskyEmbedImages.Main
const validateImages = AppBskyEmbedImages.validateMain

// Get some data somehow
declare const data: unknown

// Validate the data against a particular schema (images here)
const result = validateImages(data)

if (result.success) {
// The `value` property was previously typed as `unknown`
// and is now properly typed as `Image`
const images = result.value
}

These methods perform data validation, making them somewhat slower than the is* utility methods. They can, however, be used in place of the is* utilities when migrating to this new version of the SDK.

New asPredicate function

The SDK exposes a new asPredicate function. This function allows converting a validate* function into a predicate function. This can be useful when working with libraries that expect a predicate function to be passed as an argument.

import { asPredicate, AppBskyEmbedImages } from '@atproto/api'

// Aliased for clarity
const Images = AppBskyEmbedImages.Main
const isValidImages = asPredicate(AppBskyEmbedImages.validateMain)

// Get an embed with unknown validity somehow
declare const embed: unknown

// The following condition will be true if, and only
// if, the value matches the `Image` interface.
if (isValidImages(embed)) {
// `embed` is of type `Images` here
}

// Similarly, the type predicate can be used to
// infer the type of an array of unknown values:
declare const someArray: unknown[]

// This will be typed as `Images[]`
const images = someArray.filter(isValidImages)
note

We decided to introduce the asPredicate function to provide an explicit way to convert validate* functions into predicate functions. More importantly, this function allowed us limit the bundle size increase that would have been caused by the introduction new isValid* utility methods as part of this release.

Removal of the [x: string] index signature

Another property of Atproto being an "open protocol" is the fact that objects are allowed to contain additional — unspecified — properties (although this should be done with caution to avoid incompatibility with properties that are added in the future). This used to be represented in the type system using a [k: string]: unknown index signature in generated interfaces. This is how the video embed used to be represented:

export interface Main {
video: BlobRef
captions?: Caption[]
alt?: string
aspectRatio?: AppBskyEmbedDefs.AspectRatio
[k: string]: unknown
}

This signature allowed for undetectable mistakes to be performed:

import { AppBskyEmbedVideo } from '@atproto/api'

const embed: AppBskyEmbedVideo.Main = {
$type: 'app.bsky.embed.video',
video: {
/* omitted */
},

// Notice the typo in `alt`, not resulting in a TypeScript error
atl: 'My video',
}

We removed that signature, requiring any unspecified fields intentionally added to be now explicitly marked as such:

import { AppBskyEmbedVideo } from '@atproto/api'

const embed: AppBskyEmbedVideo.Main = {
$type: 'app.bsky.embed.video',
video: {
/* omitted */
},

// Next line will result in the following
// TypeScript error: "Object literal may only
// specify known properties, and 'atl' does not
// exist in type 'Main'"
atl: 'My video',

// Unspecified fields must now be explicitly
// marked as such:

// @ts-expect-error - custom field
comExampleCustomProp: 'custom value',
}

Other considerations

When upgrading, please make sure that your project does not depend on multiple versions of the @atproto/* packages. Use resolutions or overrides in your package.json to pin the dependencies to the same version.

Recap

We hope this release helps you build better codebases with improved type safety. During our own migration, we found and fixed a few small bugs, and we believe these changes will benefit the entire developer community.

Migration TL;DR:

  • Need to be absolutely sure of your data? Use asPredicate or validate* utilities.
  • Using data from the Bluesky app view? You can use is* utilities.
  • Building lex objects for writing? Make sure you use $Typed when building those.

Happy coding!

Looking Back At 2024 AT Protocol Development

· 6 min read

In May 2024, we published a 2024 Protocol Roadmap, and we want to give an end-of-year update. We will follow up soon with a forward-looking roadmap for 2025.

In the big picture, most of the public data aspects of the protocol have now been designed and implemented. The last missing pieces are nearing completion, and we do not foresee disruptive changes or additions which would impact interoperability. Now is a great time to start building on the protocol and assembling independent infrastructure.

A lot of progress was made in 2024! Some large protocol milestones include:

  • Open PDS federation in the live network: At the time of roll-out, Bluesky initially required pre-registration and placed a limit on the number of hosted accounts. These limits have since been removed, and now any PDS can participate in the live network without prior coordination. We encourage the growth of independent PDS instances of any size. There are some rate-limits in place to prevent bot farms, but we will increase them when needed to accommodate PDS growth.
  • Labeling: We launched the stackable moderation system with labels and reports.
  • Account Migration: Users can migrate their account to alternate PDSes using command-line tooling.
  • Generic Service Proxying: Client XRPC requests to the PDS can be proxied on to arbitrary service providers, using inter-service auth, via a client-controlled HTTP header.
  • Flexible Record Schemas: Data records of any schema can be written to atproto repositories, with "eager" Lexicon validation controlled by query parameter.
  • Account Deactivation: This was implemented at the protocol layer, along with an overhaul of the #identity and #account firehose events.
  • Initial OAuth Support: This was launched in production, with extensive documentation.
  • Jetstream: We shipped an alternative WebSocket API for the firehose, which uses simple JSON and record-level operations (as opposed to "commits"). This makes it easier for independent developers to process the Bluesky firehose.

Lexicon Resolution

One of the more recently-completed components is Lexicon resolution. This is the mechanism for looking up the schema of new data by NSID. After feedback on a public proposal, we settled on a design that uses DNS TXT records that map to a DID, then schemas stored as records in an atproto repo. We have some early implementations which prove out the design.

We plan on writing this up as a formal specification, and would like to build tooling to support publishing, mirroring, discovery, and integration of Lexicons.

Auth and OAuth

The OAuth announcement blog post gives a good overview of the progress made in 2024, and links out to developer resources. Server-side support has been implemented in the Bluesky PDS distribution, and several independent projects are using it for login in the live network. We iterated on our design to align with the Web Auth Working Group of the IETF, and will make small changes as needed to stay in alignment.

One of the missing pieces is Auth Scopes, which will allow more granular and flexible Authorization grants. We have made a fair amount of design progress on this mechanism but still have a few issues to resolve. Keep an eye out for a public summary of this work soon.

Based on the OAuth Roadmap, we are still in the first phase. Apart from Scopes, we have feedback from developers that token lifetimes may need tuning.

Apart from OAuth, we are increasingly interested in a more powerful and flexible auth token mechanism, but have not started any design or planning work yet.

Sync, Firehose, and Backfill

As the overall network has scaled to over 26 million accounts, resource costs around Relays and the firehose mechanism have become more urgent.

The initial Relay design required a full mirror of all repository data (records and MST nodes) to fully verify each commit message. This meant that disk consumption increased both with the number of accounts, and the amount of data each account stored. This was considered relatively affordable, and further scaling could be achieved with infrastructure sharding.

However, we are currently exploring a "non-archival" relay design which is significantly cheaper to operate, even at full network scale. This will simplify the role of relays in the network, replace the tooBig mechanism for large commits, and clarify what a downstream service needs to do to synchronize data reliably.

We are also working on improved ergonomics for working with subsets of data in the network. In particular, new small applications (Lexicon schemas) should be able to start small. It is important to be able to backfill only the relevant data already in the network and then subscribe to just a subset of data downstream of the firehose.

Specifications and Ecosystem

We refreshed the AT Protocol website, and the written specifications were expanded to cover the Firehose, Blobs, Account Hosting, and more.

Bluesky sent a representative to IETF 120 in Vancouver, Canada. We have started participating in the OAuth and DNSOP working groups, and have been discussing timelines and strategy for the standards process with members of the community.

DASL is an independent effort to define a coherent subset of the IPLD specifications which projects can build upon without the full complexity and large implementation surface of those systems. These align very tightly with the atproto data model! It should be possible for atproto software to build on top of DASL implementation libraries (which have fewer dependencies than full IPLD implementations), and there is potential for collaboration within formal standards bodies.

lexicon.community is an independent effort to develop reusable atproto Lexicon schemas in a collaborative manner. They have a defined governance and contribution model, and an initial schema declared for bookmarks.

What Else?

A few important areas did not see much change in 2024. The PLC identity system has been operating reliably, but the plc.directory service needs to be decentralized through technical and governance improvements. Protocol features to support private content in the network continue to be a top external request, and are likewise a priority within the team. E2EE DMs are the planned successor to the initial DM system, but we have not begun work on them.

All the above are important and will take time to get right. We will share a forward-looking protocol roadmap in the near future, covering these projects, decentralization efforts, and more.

In the meanwhile, we encourage you to check in on both official and community channels for updates on AT Protocol. An increasing share of development is happening out in the ecosystem, with more projects and organizations getting started by the week: