<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://docs.bsky.app/blog</id>
    <title>Bluesky Blog</title>
    <updated>2026-01-24T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://docs.bsky.app/blog"/>
    <subtitle>Bluesky Blog</subtitle>
    <icon>https://docs.bsky.app/img/favicon.png</icon>
    <entry>
        <title type="html"><![CDATA[Upcoming Relay Transition]]></title>
        <id>https://docs.bsky.app/blog/relay-rollout</id>
        <link href="https://docs.bsky.app/blog/relay-rollout"/>
        <updated>2026-01-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[In coming days we are finally going to transition the bsky.network firehose endpoint to a newer relay implementation. This should not have a significant impact on most consumers.]]></summary>
        <content type="html"><![CDATA[<p>In coming days we are finally going to transition the <code>bsky.network</code> firehose endpoint to a newer relay implementation. This should not have a significant impact on most consumers.</p>
<p>More specifically, we are tagetting the morning <em>Tuesday, January 27th</em> (US/Pacific timezone). The <code>bsky.network</code> firehose will be switched from the <code>narelay.pop2.bsky.network</code> relay instance to the <code>relay1.us-west.bsky.network</code> relay instance.</p>
<p>This transition was <a href="https://docs.bsky.app/blog/relay-sync-updates">previously announced</a> but postponed until now. The majority of Bluesky internal services transitioned to the new relay instances in the past week.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-will-this-impact-relay-consumers">How will this impact relay consumers?<a href="https://docs.bsky.app/blog/relay-rollout#how-will-this-impact-relay-consumers" class="hash-link" aria-label="Direct link to How will this impact relay consumers?" title="Direct link to How will this impact relay consumers?">​</a></h2>
<p>If you are connected to the <code>bsky.network</code> hostname, your websocket connection may drop, and the cursor sequence will jump forward by a significant delta. If your client implements auto-reconnection, no manual intervention should be necessary, and impact should be minimal.</p>
<p>It is likely that some events will be duplicated with new (higher) sequence numbers. The per-account revision (<code>rev</code>) field can be used to de-duplicate events at the account level. For many use-cases (such as bots, feed generators, etc), re-processing a small number of events should not be problematic.</p>
<p>The backfill window of the <code>bsky.network</code> instance should reflect what is seen on the firehose. That is, reconnecting with an "old" (lower) cursor value should not result in replaying the full "new relay" 72 hour backfill window. This is because an intermediate service (<code>rainbow</code>) is inserted between the relay backend and the public endpoint.</p>
<p>Operators can implement the transition on their own schedule by connecting directly to specific relay instances:</p>
<ul>
<li><code>narelay.pop2.bsky.network</code> is the current relay behind <code>bsky.network</code>. It has been in operation since Fall 2024 and runs the older "bigsky" implementation. The current sequence is around <code>17502400000</code> (aka, 17 billion). This instance will be shut down a week or two after the transition.</li>
<li><code>relay1.us-west.bsky.network</code> will be the new relay behind <code>bsky.network</code>. The current sequence is around <code>26653242499</code> (aka, 26 billion).</li>
<li><code>relay1.us-east.bsky.network</code> is an alternative relay running in a separate data center. The current sequence is around <code>26653242087</code> (aka, 26 billion).</li>
</ul>
<p>Note that the east/west sequence numbers are similar (because they started at the same time), but are not identical. The content is very similar (they are both full-network relays), but are not seamlessly interchangeable.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-about-jetstream-consumers">What about Jetstream Consumers?<a href="https://docs.bsky.app/blog/relay-rollout#what-about-jetstream-consumers" class="hash-link" aria-label="Direct link to What about Jetstream Consumers?" title="Direct link to What about Jetstream Consumers?">​</a></h2>
<p>Jetstream uses timestamp cursors, so no observable cursor jump will happen. Consumers may see a small number of duplicate/replay events around the transition.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-will-this-impact-pds-hosts">How will this impact PDS hosts?<a href="https://docs.bsky.app/blog/relay-rollout#how-will-this-impact-pds-hosts" class="hash-link" aria-label="Direct link to How will this impact PDS hosts?" title="Direct link to How will this impact PDS hosts?">​</a></h2>
<p>There should not be any noticeable impact from this transition. The new relay instances have already been consuming from all PDS hosts for many months, "request crawl" messages are mirrored, and rate limits have been synchronized between old and new relay instances.</p>
<p>The new relay implementation does include support for string verification of "MST inversion proofs", part of the <a href="https://github.com/bluesky-social/proposals/tree/main/0006-sync-iteration" target="_blank" rel="noopener noreferrer">Sync 1.1</a> protocol changes. However, strict verification will still be disabled for now.</p>
<p>We do intent to enable strict MST verification in the near future. The reference PDS implementation has included the required changes for some time. We will monitor which PDS instances and implementations might be impacted and work with developers and operators before making that change, and will make public announcements ahead of time.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-are-we-doing-this">Why are we doing this?<a href="https://docs.bsky.app/blog/relay-rollout#why-are-we-doing-this" class="hash-link" aria-label="Direct link to Why are we doing this?" title="Direct link to Why are we doing this?">​</a></h2>
<p>The new relay includes support for Sync 1.1 protocol features, such as the <code>#sync</code> message type and additional metadata supporting "MST inversion". It also fixes a handful of bugs that have caused operational problems with individual PDS implementations.</p>
<p>The new implementation also removes a huge amount of deprecated code related to "archival" relay operation and indexing of record data in the relay itself. It should be easier to maintain and develop new features for the relay going forward, including smarter rate-limiting and resolving connection issues with upstream PDS instances.</p>]]></content>
        <category label="firehose" term="firehose"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Introducing Tap: Repository Synchronization Made Simple]]></title>
        <id>https://docs.bsky.app/blog/introducing-tap</id>
        <link href="https://docs.bsky.app/blog/introducing-tap"/>
        <updated>2025-12-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Just about every app built on AT needs data from a repository at some point. For many use cases – feed generators, labelers, bots – streaming live data through a Relay or Jetstream works well. But some applications need to go beyond what Jetstream was designed for, like tracking specific subsets of a repo, automatically backfilling a database when adding new repos to monitor, or even mirroring the entire Atmosphere.]]></summary>
        <content type="html"><![CDATA[<p>Just about every app built on AT needs data from a repository at some point. For many use cases – feed generators, labelers, bots – streaming live data through a Relay or Jetstream works well. But some applications need to go beyond what Jetstream was designed for, like tracking specific subsets of a repo, automatically backfilling a database when adding new repos to monitor, or even mirroring the entire Atmosphere.</p>
<p>We recently released <a href="https://github.com/bluesky-social/indigo/blob/main/cmd/tap/README.md" target="_blank" rel="noopener noreferrer">Tap</a>, a tool designed to handle the hard parts of repo synchronization, so you can focus on building your application.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-challenge-of-at-sync">The Challenge of AT Sync<a href="https://docs.bsky.app/blog/introducing-tap#the-challenge-of-at-sync" class="hash-link" aria-label="Direct link to The Challenge of AT Sync" title="Direct link to The Challenge of AT Sync">​</a></h2>
<p>AT provides two primary mechanisms for syncing repositories: the Relay firehose of real-time events, and direct PDS access to full repos via <code>com.atproto.sync.getRepo</code>. Most applications consume events directly from an upstream Relay or Jetstream, and this presents a new set of challenges:</p>
<p><strong>Backfill is tricky.</strong> When you start tracking a new repo, you likely need to fetch at least some of the current records in that repo, possibly the full repo. Neither the Relay nor Jetstream provides a built-in utility for doing this, leaving it as an exercise for the developer. And once you do have the full backfill, you then need to cut over to fetching live events, requiring careful cursor management to make sure you’re not duplicating or missing data.</p>
<p><strong>Recovery is fragile.</strong> If your repo gets into a desynchronized state for whatever reason, it’s often hard to recover gracefully. Without some explicit recovery logic, you’ll simply be missing data forever. Cursor management is a tricky affair given the stateless design of the repo event stream making it easier for repos to land in a desynchronized state.</p>
<p><strong>WebSocket connections aren’t always the right solution.</strong> Currently, syncing requires maintaining a stateful WebSocket connection, which on its own can be hard to manage and requires custom infrastructure separate from most web application stacks. And most serverless platforms don’t have an easy solution for deploying these types of long-lived connections.</p>
<p><strong>The protocol itself is demanding.</strong> Proper AT involves binary encodings, identity resolution, signature verification, and the semantics of <a href="https://github.com/bluesky-social/proposals/tree/main/0006-sync-iteration" target="_blank" rel="noopener noreferrer">Sync 1.1</a>. <a href="https://docs.bsky.app/blog/jetstream" target="_blank" rel="noopener noreferrer">Jetstream was built to make sync more approachable</a>, but required developers to implicitly trust the output from Jetstream and clients end up trading off the benefits of doing actually authenticated sync.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-tap-does">What Tap Does<a href="https://docs.bsky.app/blog/introducing-tap#what-tap-does" class="hash-link" aria-label="Direct link to What Tap Does" title="Direct link to What Tap Does">​</a></h2>
<p>Put simply, Tap is the all-in-one tool for synchronizing subsets of the Atmosphere, or even creating a full copy of the Atmosphere if you need it. Tap is a single-tenant service, written in Go, that subscribes to a Relay and outputs filtered, verified events of repositories. Think of it as a specialized synchronization tool that sits between the <a href="https://atproto.com/specs/sync#firehose" target="_blank" rel="noopener noreferrer">full network firehose</a> and your application.</p>
<p>The goal of Tap is to handle the complexity of repo synchronization, with features that manage:</p>
<p><strong>Automatic backfill.</strong> When you begin tracking a repository, Tap fetches its complete history before delivering live events</p>
<p><strong>Verification.</strong> Tap handles MST integrity checks, identity signatures, and all the cryptographic validation required by the protocol.</p>
<p><strong>Recovery.</strong> If a repo becomes desynchronized, Tap automatically resyncs from the authoritative PDS.</p>
<p><strong>Flexible delivery and connection.</strong> Choose between a WebSocket connection with acknowledgements, fire-and-forget mode, or webhooks for serverless applications.</p>
<p><strong>Filtered output.</strong> Receive events only for the repositories and collections you care about, delivered as simple JSON.</p>
<p>Here’s a basic workflow to track a single repo:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Start Tap (defaults to port 2480, SQLite backend)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">go run ./cmd/tap --disable-acks=true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Connect to receive events</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">websocat ws://localhost:2480/channel</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Add repositories to track</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">curl -X POST http;//localhost:2480/repos/add \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  -H "Content-Type: application/json" \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  -d '{"dids": ["did:plc:ewvi7nxzyoun6zhxrhs64oiz"]}'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Once you add the repository via the <code>repos/add</code> API method, Tap begins backfilling its history automatically. Historical events arrive marked with <code>live:false</code>, followed by any buffered live events that occurred during the backfill, and then new events streamed in time marked with <code>live: true</code>. Your application receives a complete, ordered view of each repository it subscribes to, without gaps.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="delivery-guarantees-and-ordering">Delivery Guarantees and Ordering<a href="https://docs.bsky.app/blog/introducing-tap#delivery-guarantees-and-ordering" class="hash-link" aria-label="Direct link to Delivery Guarantees and Ordering" title="Direct link to Delivery Guarantees and Ordering">​</a></h2>
<p>Tap takes on the responsibility of cursor management and provides clear delivery guarantees. Events are delivered <strong>at least once</strong> — if Tap crashes before receiving an event acknowledgment, events will be re-delivered after Tap is restarted.</p>
<p>Ordering guarantees are per-repo rather than global. For live events from a particular repo, Tap will wait until the previous event is acked before sending the next event. When backfilling or resyncing a repo, Tap will send all processed records concurrently. This design allows a historical backfill to run quickly, while ensuring live events maintain strict ordering.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="network-boundaries-and-filtering">Network Boundaries and Filtering<a href="https://docs.bsky.app/blog/introducing-tap#network-boundaries-and-filtering" class="hash-link" aria-label="Direct link to Network Boundaries and Filtering" title="Direct link to Network Boundaries and Filtering">​</a></h2>
<p>Tap can operate in three modes for determining which repos to track:</p>
<p><strong>Dynamically configured (default):</strong> Start with zero tracked repositories and add specific DIDs via the <code>repos/add</code> endpoint as needed.</p>
<p><strong>Collection signal mode:</strong> Track all repositories that contain at least one record in a specified collection. Set <code>TAP_SIGNAL_COLLECTION=com.example.nsid</code> to automatically discover and track repositories when they create records in your application’s namespace. A common strategy that Atmosphere apps use is to have a “profile” or “declaration” which suggests that a given repo uses that application. For example, Bluesky creates an <code>app.bsky.actor.profile</code>  record for all Bluesky users.</p>
<p><strong>Full network mode:</strong> Track all repositories across the entire network with <code>TAP_FULL_NETWORK=true</code>. This is a very resource intensive mode and should only be used if you actually want a complete mirror of the entire Atmosphere! Expect terabytes of data and days to weeks for the initial backfill. Our benchmarks suggest Tap can handle 35-45k events per second during a full network backfill.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="when-to-use-tap">When to Use Tap<a href="https://docs.bsky.app/blog/introducing-tap#when-to-use-tap" class="hash-link" aria-label="Direct link to When to Use Tap" title="Direct link to When to Use Tap">​</a></h2>
<p>Tap isn’t meant to replace Jetstream for all use cases – if Jetstream is working fine for you, you may not even need Tap. Jetstream isn’t going anywhere! Since Tap does require more operational overhead, it’s important to understand when you might want to use it with your app:</p>
<ul>
<li>Automatic historical backfill when tracking new repositories</li>
<li>Native webhook support for serverless architectures</li>
<li>Full network backfill and mirroring capabilities for research and analysis</li>
<li>Guaranteed delivery with acknowledgement mode</li>
<li>For tracking specific subsets of repositories without needing to process the full firehose</li>
</ul>
<p>Tap is particularly valuable for applications that need precise control over which data they sync, applications that scale up repository tracking (such as social discovery tools), and research or infrastructure projects that require a complete mirror of either the entire or some well-defined subset of the  Atmosphere.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="full-atmosphere-mirroring-as-a-principle">Full Atmosphere Mirroring as a Principle<a href="https://docs.bsky.app/blog/introducing-tap#full-atmosphere-mirroring-as-a-principle" class="hash-link" aria-label="Direct link to Full Atmosphere Mirroring as a Principle" title="Direct link to Full Atmosphere Mirroring as a Principle">​</a></h2>
<p>One design goal for Tap worth highlighting is that we consider it a failure of AT as an open network if any third party with adequate resources cannot backfill the entire network. This stands in contrast to centralized, monolithic social networks that would typically only offer a real-time event stream, if any firehose access at all, making any kind of longitudinal analysis difficult (or impossible) without special agreements.</p>
<p>Tap makes this philosophical principle practical. The ability to provision an app and maintain your own complete mirror of the Atmosphere enables research, verification, and entire new classes of applications that require historical context. Adding real-time synchronization enables new services built on both historical depth and current data.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="deployment-and-client-integration">Deployment and Client Integration<a href="https://docs.bsky.app/blog/introducing-tap#deployment-and-client-integration" class="hash-link" aria-label="Direct link to Deployment and Client Integration" title="Direct link to Deployment and Client Integration">​</a></h2>
<p>Tap is a single Go binary backed by a relational database for managing repo metadata (SQLite by default for simple deployments or Postgres for larger scale apps). Once received, records are handed off to the app to be indexed.</p>
<p>For TypeScript developers, we’re releasing the <code>@atproto/tap</code> client library to make consuming Tap events in your TypeScript app even easier. We’re planning  a “typed indexer” that merges this work with the recent Lexicon SDK rewrite, providing end-to-end type safety from protocol and Lexicon definitions to event stream handers.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://docs.bsky.app/blog/introducing-tap#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<p>Tap is in beta and we welcome you to try it out with your application and let us know if you run into any issues. We’ve included comprehensive documentation in the project <a href="https://github.com/bluesky-social/indigo/blob/main/cmd/tap/README.md" target="_blank" rel="noopener noreferrer">README</a>, a <a href="https://github.com/bluesky-social/indigo/blob/main/cmd/tap/RAILWAY_DEPLOY.md" target="_blank" rel="noopener noreferrer">3-minute deployment guide for Railway</a>, and a <a href="https://github.com/bluesky-social/atproto/blob/main/packages/tap/README.md" target="_blank" rel="noopener noreferrer">TypeScript library</a> showcasing event handlers.</p>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Request For Comments: A secure contact import scheme for social networks]]></title>
        <id>https://docs.bsky.app/blog/contact-import-rfc</id>
        <link href="https://docs.bsky.app/blog/contact-import-rfc"/>
        <updated>2025-11-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[This article outlines plans for a future Bluesky feature \- it doesn’t exist yet\! By sharing our ideas early, we hope to solicit feedback from the community.]]></summary>
        <content type="html"><![CDATA[<p><em>This article outlines plans for a future Bluesky feature - it doesn’t exist yet! By sharing our ideas early, we hope to solicit feedback from the community.</em></p>
<blockquote>
<p><strong>TL;DR:</strong> This is a secure way to find people you know by sharing your phone’s contacts with Bluesky. Don’t want to use this? No sweat! A key feature of this design is that it’s <strong>double opt-in</strong>. If you never use this feature, you will never be findable using your phone number.</p>
</blockquote>
<p>Anyone building a social app has to solve the “cold start” problem of bootstrapping dense social graphs. Although Bluesky is a lively place with over 40 million users, we want to connect even more people with their friends.</p>
<p>A common industry solution to friend-discovery is to allow users to upload their phone contact lists to find and follow other users. Unfortunately, these systems have been shown to <a href="https://contact-discovery.github.io/" target="_blank" rel="noopener noreferrer">leak users’ phone numbers</a>. Bluesky is unwilling to ship any feature that exposes PII in that way.</p>
<p>Another concern is consensual usage. The contact upload should only be used for the purpose of finding friends. And then, what if a friend uploads your phone number without you knowing? You shouldn’t show up in these phone-number matches without okaying it yourself.</p>
<p>These issues have meant that until now, we have foregone this common utility.</p>
<p>We believe we have now found a solution to the security risks of contact-list imports, which we will describe in this blog post. We offer this initial write-up to solicit critical feedback from the community. If you have questions or concerns about the design of this scheme, please raise them <a href="https://github.com/bluesky-social/atproto/discussions/4356" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="requirements">Requirements<a href="https://docs.bsky.app/blog/contact-import-rfc#requirements" class="hash-link" aria-label="Direct link to Requirements" title="Direct link to Requirements">​</a></h2>
<p>Handling user contact information has security and privacy implications. If we offer a phone-number matching service, we want it to have these properties:</p>
<ul>
<li><strong>Truly “opt-in”</strong>. This feature isn’t for everyone, and those who don’t want it can ignore it without hassle.</li>
<li><strong>Revocable consent</strong>. If you change your mind later, all uploaded data can be removed from our servers.</li>
<li><strong>Specificity</strong>. Uploaded data will <em>only</em> be used to help you find your contacts on Bluesky, and for no other purposes.</li>
<li><strong>Security</strong>. We want to keep uploaded phone numbers confidential, even in worst-case scenarios, like our servers getting hacked. Enumeration attacks should likewise not be feasible.</li>
</ul>
<p>In terms of threat modeling, we want to protect the following information:</p>
<ul>
<li>Whether a particular phone number belongs to any Bluesky user at all</li>
<li>The mapping of specific phone numbers to specific users</li>
<li>The uploaded contacts list of each user</li>
</ul>
<p>And we want to protect against the following types of threat actors:</p>
<ul>
<li>An attacker with access to the external API interface</li>
<li>An attacker with unauthorized access to internal Bluesky infrastructure</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="threat-1-enumeration-attacks">Threat 1: Enumeration attacks<a href="https://docs.bsky.app/blog/contact-import-rfc#threat-1-enumeration-attacks" class="hash-link" aria-label="Direct link to Threat 1: Enumeration attacks" title="Direct link to Threat 1: Enumeration attacks">​</a></h2>
<p>The most likely threat is an attacker with access to the external API interface trying to enumerate which phone numbers belong to which users. This might sound easy to defend against, but it’s deceptively tricky.</p>
<p>Imagine we have a magical, perfectly secure “black box” computer system. Users upload their contact lists to this system, and nobody can take them back out again. But, the uploader gets back a list of accounts whose phone numbers were on the list in the form of “suggested follows”.</p>
<p>An attacker uploads a contact list filled with 10,000 random phone numbers. There’s only a finite number of phone numbers in the world (on the order of 8 billion), so eventually their random number list will include the phone number of a legitimate user. The attacker gets a notification telling them that a friend has been found, giving them the opportunity to follow this user.</p>
<p>At this point, the attacker knows that this user’s phone number is somewhere on their list of 10,000 numbers, but they don’t know which one. The attacker creates a new account and re-uploads <em>half</em> of the initial list, 5,000 phone numbers. If the same user is found again, they know their phone number is in that half of the list; if not, it’s the other half. This process is repeated <math><mrow><mi>O</mi><mo form="prefix" stretchy="false">(</mo><mrow><mi>log</mi><mo>⁡</mo><mspace width="0.1667em"></mspace></mrow><mi>n</mi><mo form="postfix" stretchy="false">)</mo></mrow></math> times, and eventually the attacker will have reduced the list of phone numbers to just one - and has successfully inferred the phone number of a random Bluesky user.</p>
<p>By scaling this attack up (say, with a bot farm), the attacker could eventually deduce the phone number of every user. Hashing and rate limiting are often used to “solve” this problem, but because there are finitely many phone numbers in the world, none of these solutions is effective. Not good!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="solving-enumeration-attacks">Solving enumeration attacks<a href="https://docs.bsky.app/blog/contact-import-rfc#solving-enumeration-attacks" class="hash-link" aria-label="Direct link to Solving enumeration attacks" title="Direct link to Solving enumeration attacks">​</a></h2>
<p>We solve the enumeration attack vector with two steps:</p>
<ol>
<li>Limit discovery to mutual contacts, and</li>
<li>Verify phone number ownership before using it in matches.</li>
</ol>
<p>We have two users, Alice and Bob. They each verify their own phone numbers before uploading their respective contact lists. For either user to get a follow recommendation, Bob’s phone number must be in Alice’s submitted contacts list, <em>and</em> Alice’s phone number must be in Bob’s submitted contacts list. An attacker’s phone number(s) will never be in the contacts list of any legitimate users, so the attacker will never discover any mutual contacts.</p>
<p>Verifying phone number ownership both increases the overall work for an attacker and prevents them from lying about their phone number.</p>
<p>The downside of this scheme is that it will reduce the overall number of matches, since both sides must participate in the contact import, but we think this is a worthwhile trade for protecting people’s privacy. In fact, we would argue that making people feel safe to use the import is pretty critical to gaining any value at all.</p>
<p>In addition to phone number verification, we need to be careful to rate-limit the number of contact numbers that can be imported by an individual user. This prevents undue load on our systems and also ensures that a user cannot attempt to enumerate who has <em>their</em> phone number as a contact.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="threat-2-database-leaks">Threat 2: Database leaks<a href="https://docs.bsky.app/blog/contact-import-rfc#threat-2-database-leaks" class="hash-link" aria-label="Direct link to Threat 2: Database leaks" title="Direct link to Threat 2: Database leaks">​</a></h2>
<p>Nobody ever wants to experience a data breach, but good security practices mean planning for the worst. The scenario is simple: somehow the service is breached, and the phone number registry is leaked. What now?</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="securing-against-database-leaks">Securing against database leaks<a href="https://docs.bsky.app/blog/contact-import-rfc#securing-against-database-leaks" class="hash-link" aria-label="Direct link to Securing against database leaks" title="Direct link to Securing against database leaks">​</a></h2>
<p>The “obvious” approach is to store the cryptographic hash of each phone number, similarly to password-handling best practices. The problem here is that there are simply not very many possible phone numbers, somewhere on the order of 8 billion. A modern GPU could crack a SHA-256-hashed phone number in under a second.</p>
<p>Brute-force-resistant hash algorithms like Argon2 are a big improvement on that, but 8 billion phone numbers is still a small search space (<math><msup><mn>2</mn><mn>33</mn></msup></math>), and attackers could construct a Rainbow Table, i.e., a reverse lookup table for every possible phone number. Unlike with password hashing, we cannot use a random salt for each phone number because then we wouldn’t be able to find matches between two users (because mismatched salts would produce different hash outputs).</p>
<p>Since we’re only trying to discover <em>mutual</em> contacts, we can store phone numbers in hashed unordered pairs, such that <code>hash(x, y) == hash(y, x)</code>. This order-independence can be achieved by sorting the pair of inputs before hashing them. Assuming there are <math><msup><mn>2</mn><mn>33</mn></msup></math> possible phone numbers, then there are <math><msup><mn>2</mn><mn>65</mn></msup></math> possible <em>pairs</em> of phone numbers. This makes brute-force attacks much more difficult, and makes Rainbow Tables much less feasible.</p>
<p><em>Note: The practical brute-force difficulty will be lower than <math><msup><mn>2</mn><mn>65</mn></msup></math> because real-world pairs of phone numbers are correlated (e.g. more likely to be from the same region). However, it is strictly more difficult to brute-force a hash of a pair of numbers than to brute-force a hash of either number individually.</em></p>
<p>Argon2 is a brute-force-resistant hash function, so that’s what we’ll be using (specifically, Argon2id). Since we cannot use a randomized salt (as would be typical of a password hashing use case), we will use a fixed salt across all hashes. This still provides a security benefit because the salt value can be stored separately from the database. In the event of a database compromise, the data would be completely useless to attackers without knowledge of the salt. Using a fixed salt like this is often referred to as a “<a href="https://en.wikipedia.org/wiki/Pepper_(cryptography)" target="_blank" rel="noopener noreferrer">pepper</a>”</p>
<p>In an ideal world, the pepper would be stored securely inside an <a href="https://en.wikipedia.org/wiki/Hardware_security_module" target="_blank" rel="noopener noreferrer">HSM</a>. However, HSM support for the Argon2 hash function is not ubiquitous, so it will have to be computed outside of an HSM. To achieve an HSM-equivalent level of overall security, we’ll apply a final HMAC to the Argon2 output, using a secret that <em>is</em> stored in an HSM (using the HMAC as a keyed hash function, rather than a MAC)</p>
<p>Argon2 provides brute-force-resistance, while the final HMAC binds the output hash to a secret stored in an HSM. The overall hash function could be summarised in pseudocode as:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">def hash(x, y):  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return HMAC(Argon2id(sort([x, y]), salt=ARGON_SECRET), key=HMAC_SECRET)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>If user A with phone number pA submits their contacts list <code>[pB, pC, pD]</code>, then we’d store the following in our database:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">hash(pA, pB) -&gt; A</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">hash(pA, pC) -&gt; A</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">hash(pA, pD) -&gt; A</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>(where <code>A</code> is user A’s user ID)</p>
<p>Later, user B submits their contacts list <code>[pE, pA]</code></p>
<p>The mapping of <code>hash(pB, pE) -&gt; B</code> is stored, but <code>hash(pB, pA)</code> matches with the earlier inserted <code>hash(pA, pB)</code>, associated with user A.</p>
<p>At this point, we’ve established that user A and user B are mutual contacts, and we can send follow recommendation notifications to each of them. <code>hash(pA, pB) -&gt; A</code> can be removed from the database now, since it’s no longer needed.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-complication-phone-number-recycling">A Complication: Phone Number Recycling<a href="https://docs.bsky.app/blog/contact-import-rfc#a-complication-phone-number-recycling" class="hash-link" aria-label="Direct link to A Complication: Phone Number Recycling" title="Direct link to A Complication: Phone Number Recycling">​</a></h2>
<p>There’s an annoying feature of phone infrastructure that causes trouble for anyone working with it, including us: after some time, disused phone numbers get reclaimed and assigned to new users. Mobile network operators are forced to do this because otherwise there wouldn’t be enough phone numbers to go around. When you set up a phone with a new SIM, there’s a good chance your “new” phone number used to belong to someone else.</p>
<p>Because of our bidirectional verification system (only discovering mutual contacts), the only way this could impact our system is if the old-owner and the new-owner of a phone number both use Bluesky, both use the contact import feature, and both have a mutual contact (i.e., there is a phone number that is common to both of their contact lists). In such a case, the new-owner would discover the old-owner as a false-positive match (and vice versa).</p>
<p>On the face of it, this is a very unlikely scenario, but it could become more statistically significant if the “mutual contact” is some widely-known phone number, like that of a popular restaurant.</p>
<p>To mitigate this, we will store an additional boolean alongside each hash, denoting whether the phone number of the inserting user is sorted first in the pair or not. So in the earlier example, we would actually store <code>hash(pA, pB) -&gt; (A, True)</code> (assuming <code>pA</code> &lt; <code>pB</code>), and so on.</p>
<p>When user B is submitting their contacts and the matching hash is discovered, the service checks that the boolean matches the expected value. If not, it ignores the match, but updates the stored user ID associated with the hash to match B.</p>
<p>The downside of this approach is that the information in the database would leak statistical information about the relative magnitude of the submitter’s phone number. For example, if user A imports 1000 phone numbers and <em>all</em> the boolean field values are True, we can be confident that user A’s phone number has a low magnitude (i.e., it probably starts with a 1). This is not useful information on its own, but it could be used to accelerate a brute-force attack.</p>
<p>We mitigate <em>this</em> by sorting according to a custom comparison function that does not produce a <a href="https://en.wikipedia.org/wiki/Total_order" target="_blank" rel="noopener noreferrer">Total Order</a>. In pseudocode:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">def intransitiveCompare(a, b):</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    assert(a != b)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return sha256(a || SEP || b) &gt; sha256(b || SEP || a)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Where <code>||</code> denotes concatenation and SEP is a separator string that is guaranteed not to be contained within the inputs. Note that this comparison function preserves <a href="https://en.wikipedia.org/wiki/Antisymmetric_relation" target="_blank" rel="noopener noreferrer">antisymmetry</a> i.e., <code>intransitiveCompare(a, b) == !intransitiveCompare(b, a)</code> for all non-equal values of a and b.</p>
<p>With this comparison function, even a hypothetical phone number of “0” would still be expected to compare greater than a random other phone number with 50% probability. And so, storing these comparison result booleans in the database does not leak any relevant information about the input phone numbers.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="summary">Summary<a href="https://docs.bsky.app/blog/contact-import-rfc#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary">​</a></h2>
<p>The following features all work together to construct a contact discovery system that is highly resistant to data misuse:</p>
<ul>
<li>Verification of the user’s own phone number</li>
<li>Only revealing mutual contacts</li>
<li>Brute-force-resistant pairwise hashing, bound to an HSM-stored secret</li>
</ul>
<p>We resist API-level enumeration, while <em>also</em> mitigating the consequences of a full database compromise, all while keeping the system (and its deployment) simple. Deployments of this design could be additionally hardened through the use of trusted computing environments, but this is out of scope for our initial plans.</p>
<p>As stated in the introduction, we offer this write-up with the hope of receiving any critiques that might highlight issues with the construction. If you have any concerns to share, please provide them <a href="https://github.com/bluesky-social/atproto/discussions/4356" target="_blank" rel="noopener noreferrer">here</a>.</p>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Protocol Check-in (Fall 2025)]]></title>
        <id>https://docs.bsky.app/blog/protocol-checkin-fall-2025</id>
        <link href="https://docs.bsky.app/blog/protocol-checkin-fall-2025"/>
        <updated>2025-10-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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\!]]></summary>
        <content type="html"><![CDATA[<p>We last shared a protocol roadmap back in <a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring" target="_blank" rel="noopener noreferrer">March 2025</a>, 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. <a href="https://bsky.app/profile/mobydickatsea.bsky.social/post/3m2xhz36kqq2p" target="_blank" rel="noopener noreferrer">Oh time, strength, cash, and patience!</a></p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-atmosphere-is-thriving">The Atmosphere is Thriving<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#the-atmosphere-is-thriving" class="hash-link" aria-label="Direct link to The Atmosphere is Thriving" title="Direct link to The Atmosphere is Thriving">​</a></h2>
<p>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.</p>
<p>What's really amazing is watching developers help other developers. Development is happening over on <a href="https://tangled.sh/" target="_blank" rel="noopener noreferrer">Tangled</a>. Devs are sharing updates through <a href="https://leaflet.pub/" target="_blank" rel="noopener noreferrer">Leaflet</a>. Projects like <a href="http://slices.network/" target="_blank" rel="noopener noreferrer">Slices</a>, <a href="https://www.microcosm.blue/" target="_blank" rel="noopener noreferrer">Microcosm</a>, <a href="https://pdsls.dev/" target="_blank" rel="noopener noreferrer">PDSls</a>, and <a href="https://graze.leaflet.pub/3m33mkloj222o" target="_blank" rel="noopener noreferrer">Graze</a> are making it easier for everyone to build. The AT Protocol Community just announced <a href="https://news.atmosphereconf.org/3m3cwwz4mpk2j" target="_blank" rel="noopener noreferrer">the second AtmosphereConf</a> this March in Vancouver. This is what decentralized development looks like. Remember: on an open protocol <a href="https://underreacted.leaflet.pub/3m23gqakbqs2j" target="_blank" rel="noopener noreferrer">we can just do things</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="big-picture">Big Picture<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#big-picture" class="hash-link" aria-label="Direct link to Big Picture" title="Direct link to Big Picture">​</a></h2>
<p>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 <a href="https://github.com/bluesky-social/proposals/tree/main/0011-auth-scopes" target="_blank" rel="noopener noreferrer">auth scopes</a> and <a href="https://github.com/bluesky-social/proposals/blob/main/0006-sync-iteration/README.md" target="_blank" rel="noopener noreferrer">Sync1.1</a>, 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.</p>
<p>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.</p>
<p>We’re pursuing this through three main tracks:</p>
<ul>
<li><strong>Developer Experience</strong>: Making AT fun and easy to work with. Product-focused devs should be able to build a new social app in a weekend.</li>
<li><strong>Governance</strong>: Ensuring that AT is something larger and longer-lived than Bluesky</li>
<li><strong>Hard Decentralization</strong>: Encouraging the emergence of more stakeholders in the network for a more resilient Atmosphere</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="developer-experience">Developer Experience<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#developer-experience" class="hash-link" aria-label="Direct link to Developer Experience" title="Direct link to Developer Experience">​</a></h2>
<p>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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="oauth-cookbooks--tutorials">OAuth Cookbooks &amp; Tutorials<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#oauth-cookbooks--tutorials" class="hash-link" aria-label="Direct link to OAuth Cookbooks &amp; Tutorials" title="Direct link to OAuth Cookbooks &amp; Tutorials">​</a></h3>
<p>OAuth is one of the trickiest parts of building on the protocol — tricky enough that Nick Gerakines is <a href="https://ti.to/ngerakines/atprotocol-oauth-masterclass-fall-2025" target="_blank" rel="noopener noreferrer">selling out courses</a> 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.</p>
<p>We're taking inspiration from <a href="https://auth0.com/" target="_blank" rel="noopener noreferrer">Auth0</a>'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.</p>
<p>We recently wrapped up our dev work on <a href="https://github.com/bluesky-social/proposals/tree/main/0011-auth-scopes" target="_blank" rel="noopener noreferrer">auth scopes and permission sets</a>. Expect an announcement and guides on how to make use of those shortly.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="lexicon-sdk">Lexicon SDK<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#lexicon-sdk" class="hash-link" aria-label="Direct link to Lexicon SDK" title="Direct link to Lexicon SDK">​</a></h3>
<p>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.</p>
<p>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 <a href="https://buf.build/" target="_blank" rel="noopener noreferrer">Buf</a>’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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="sync-tool">Sync Tool<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#sync-tool" class="hash-link" aria-label="Direct link to Sync Tool" title="Direct link to Sync Tool">​</a></h3>
<p>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.</p>
<p>We’re continuing to roll out <a href="https://github.com/bluesky-social/proposals/tree/main/0006-sync-iteration" target="_blank" rel="noopener noreferrer">Sync1.1</a> on the producer (Relay) side, but a fully spec-compliant consumer for it still doesn’t exist. Most developers currently rely on <a href="https://github.com/bluesky-social/jetstream" target="_blank" rel="noopener noreferrer">Jetstream</a> 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.</p>
<p>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!</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="website">Website<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#website" class="hash-link" aria-label="Direct link to Website" title="Direct link to Website">​</a></h3>
<p>We’re going to be giving <a href="http://atproto.com/" target="_blank" rel="noopener noreferrer">atproto.com</a> 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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="governance">Governance<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#governance" class="hash-link" aria-label="Direct link to Governance" title="Direct link to Governance">​</a></h2>
<p>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.</p>
<p>To that end, we have 3 major developments:</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="patent-pledge">Patent Pledge<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#patent-pledge" class="hash-link" aria-label="Direct link to Patent Pledge" title="Direct link to Patent Pledge">​</a></h3>
<p>We announced our <a href="https://bsky.social/about/blog/10-01-2025-patent-pledge" target="_blank" rel="noopener noreferrer">Patent Non-Aggression Pledge</a> 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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="independent-plc-organization">Independent PLC Organization<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#independent-plc-organization" class="hash-link" aria-label="Direct link to Independent PLC Organization" title="Direct link to Independent PLC Organization">​</a></h3>
<p>We announced in September that we were working to <a href="https://docs.bsky.app/blog/plc-directory-org" target="_blank" rel="noopener noreferrer">establish an independent organization</a> to operate the PLC (Public Ledger of Credentials) Directory. PLC is the most common identity method for accounts in the Atmosphere.</p>
<p>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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="ietf">IETF<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#ietf" class="hash-link" aria-label="Direct link to IETF" title="Direct link to IETF">​</a></h3>
<p>We’re hoping to take pieces of <a href="https://docs.bsky.app/blog/taking-at-to-ietf" target="_blank" rel="noopener noreferrer">AT to the IETF</a>. We've submitted <a href="https://datatracker.ietf.org/doc/draft-holmgren-at-repository/" target="_blank" rel="noopener noreferrer">Internet Drafts</a> on the IETF Datatracker and established a <a href="https://mailarchive.ietf.org/arch/browse/atp/" target="_blank" rel="noopener noreferrer">mailing list</a>. We're hoping to establish a working group and towards that end, have requested a <a href="https://datatracker.ietf.org/doc/bofreq-newbold-authenticated-transfer/" target="_blank" rel="noopener noreferrer">Birds of a Feather</a> 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 <a href="https://discourse.atprotocol.community/t/update-on-timing-and-plan-for-montreal/164" target="_blank" rel="noopener noreferrer">community forum</a> 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!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="hard-decentralization">Hard Decentralization<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#hard-decentralization" class="hash-link" aria-label="Direct link to Hard Decentralization" title="Direct link to Hard Decentralization">​</a></h2>
<p>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.</p>
<p>To support the goal of hard decentralization, we're also tackling some specific technical challenges.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="improving-non-bluesky-pds-hosting">Improving Non-Bluesky PDS Hosting<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#improving-non-bluesky-pds-hosting" class="hash-link" aria-label="Direct link to Improving Non-Bluesky PDS Hosting" title="Direct link to Improving Non-Bluesky PDS Hosting">​</a></h3>
<p>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.</p>
<p>We recently <a href="https://docs.bsky.app/blog/incoming-migration" target="_blank" rel="noopener noreferrer">enabled account migration back to bsky.social</a>. 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 <a href="https://blue.mackuba.eu/stats/" target="_blank" rel="noopener noreferrer">uptick in users posting from non-Bluesky PDSes</a>.</p>
<p>Some developers in the network have launched account migration tools that make it easier for non-technical users to migrate between hosts. Examples include <a href="https://pdsmoover.com/" target="_blank" rel="noopener noreferrer">PDS MOOver</a> and <a href="https://tektite.cc/" target="_blank" rel="noopener noreferrer">Tektite</a>. We believe that the next step is to introduce an account migration UI into the PDS itself.</p>
<p>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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="technical-improvements-to-plc">Technical Improvements to PLC<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#technical-improvements-to-plc" class="hash-link" aria-label="Direct link to Technical Improvements to PLC" title="Direct link to Technical Improvements to PLC">​</a></h3>
<p>While we’re working to move <a href="http://plc.directory/" target="_blank" rel="noopener noreferrer">PLC</a> out into an independent organization, we’re also planning some technical improvements to PLC to make it more auditable.</p>
<p>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.</p>
<p>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.</p>
<p>This work is building towards the introduction of Transparency Logs (tlogs). Check out <a href="https://sunlight.dev/" target="_blank" rel="noopener noreferrer">Sunlight</a> 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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="alternate-infrastructure">Alternate Infrastructure<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#alternate-infrastructure" class="hash-link" aria-label="Direct link to Alternate Infrastructure" title="Direct link to Alternate Infrastructure">​</a></h3>
<p>We’re excited to see that more and more devs are experimenting with running alternate infrastructure in the network. <a href="https://blackskyweb.xyz/" target="_blank" rel="noopener noreferrer">Blacksky</a> currently runs a full-network relay (using the <a href="https://github.com/blacksky-algorithms/rsky" target="_blank" rel="noopener noreferrer">rksy-relay</a> implementation!), and is working on a full-network Bluesky appview. Futur showed us all that it was possible with <a href="https://github.com/zeppelin-social" target="_blank" rel="noopener noreferrer">Zeppelin</a>, a full network appview that is now decommissioned. And <a href="https://reddwarf.whey.party/" target="_blank" rel="noopener noreferrer">Red Dwarf</a> 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 <a href="https://microcosm.blue/" target="_blank" rel="noopener noreferrer">Microcosm</a>.</p>
<p>Please reach out to us if you’re working on running alternate infrastructure. We’re eager to lend a hand.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="private-data">Private Data<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#private-data" class="hash-link" aria-label="Direct link to Private Data" title="Direct link to Private Data">​</a></h2>
<p>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.</p>
<p>We continue to have internal discussions around private data. Paul <a href="https://pfrazee.leaflet.pub/3lzhmtognls2q" target="_blank" rel="noopener noreferrer">shared</a> some <a href="https://pfrazee.leaflet.pub/3lzhui2zbxk2b" target="_blank" rel="noopener noreferrer">leaflets</a> 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 <a href="https://discourse.atprotocol.community/t/introductions-and-kick-off/37" target="_blank" rel="noopener noreferrer">Private Data Working Group</a> to explore some designs for how the system could work.</p>
<p>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.</p>
<p>For E2EE DMs, <a href="https://www.germnetwork.com/" target="_blank" rel="noopener noreferrer">Germ</a> has put together a lovely app built on MLS (Messaging Layer Security) with an AT integration that’s in beta.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="keep-up-with-the-atmosphere">Keep up with the Atmosphere<a href="https://docs.bsky.app/blog/protocol-checkin-fall-2025#keep-up-with-the-atmosphere" class="hash-link" aria-label="Direct link to Keep up with the Atmosphere" title="Direct link to Keep up with the Atmosphere">​</a></h2>
<p>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:</p>
<ul>
<li>Follow the official <a href="https://bsky.app/profile/atproto.com" target="_blank" rel="noopener noreferrer">@atproto.com</a> Bluesky account.</li>
<li>Follow the community-run <a href="https://bsky.app/profile/atprotocol.dev" target="_blank" rel="noopener noreferrer">@atprotocol.dev</a> account</li>
<li>Contribute to or read <a href="https://github.com/bluesky-social/atproto/discussions" target="_blank" rel="noopener noreferrer">discussions on Github</a></li>
<li>Check out the <a href="https://connectedplaces.leaflet.pub/" target="_blank" rel="noopener noreferrer">Atmosphere report</a>, an independent newsletter (now on Leaflet!)</li>
</ul>]]></content>
        <category label="updates" term="updates"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Enabling Account Migration Back to Bluesky’s PDS]]></title>
        <id>https://docs.bsky.app/blog/incoming-migration</id>
        <link href="https://docs.bsky.app/blog/incoming-migration"/>
        <updated>2025-09-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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.]]></summary>
        <content type="html"><![CDATA[<p>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.</p>
<p>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.</p>
<p>This does not yet allow users who have never had a <code>bsky.social</code> account to migrate to our PDS. The migration flow works the same as described in the <a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md" target="_blank" rel="noopener noreferrer">Account Migration</a> documentation, but instead of creating a new account, you’ll log into your existing <code>bsky.social</code> 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 <code>bsky.social</code>.</p>
<p>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 <code>bsky.social</code>, improvements to the PDS distribution - especially for mid-sized deployments, and auto-scaling rate limits at the Relay.</p>]]></content>
        <category label="federation" term="federation"/>
        <category label="updates" term="updates"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Taking AT to the IETF]]></title>
        <id>https://docs.bsky.app/blog/taking-at-to-ietf</id>
        <link href="https://docs.bsky.app/blog/taking-at-to-ietf"/>
        <updated>2025-09-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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.]]></summary>
        <content type="html"><![CDATA[<p>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.</p>
<p>In particular, we’ve submitted two Internet-Drafts:</p>
<p><a href="https://datatracker.ietf.org/doc/draft-holmgren-at-repository/" target="_blank" rel="noopener noreferrer">Authenticated Transfer Repository and Synchronization</a>: a proposed standard that specifies the AT repository format and sync semantics</p>
<p><a href="https://datatracker.ietf.org/doc/draft-newbold-at-architecture/" target="_blank" rel="noopener noreferrer">Authenticated Transfer: Architecture Overview</a>: an informational draft that goes over the architecture of the broader network and describes how the repository fits into it.</p>
<p>Just today, we were approved for a Birds of a Feather (BOF) session at <a href="https://www.ietf.org/meeting/124/" target="_blank" rel="noopener noreferrer">IETF 124</a> in Montreal from November 1-7. Details on the BOF can be found <a href="https://datatracker.ietf.org/doc/bofreq-newbold-authenticated-transfer/" target="_blank" rel="noopener noreferrer">Here</a>.</p>
<p>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.</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-were-planning-to-bring-and-what-were-not">What We’re Planning to Bring (and What We’re Not)<a href="https://docs.bsky.app/blog/taking-at-to-ietf#what-were-planning-to-bring-and-what-were-not" class="hash-link" aria-label="Direct link to What We’re Planning to Bring (and What We’re Not)" title="Direct link to What We’re Planning to Bring (and What We’re Not)">​</a></h2>
<p>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.</p>
<p>A few reasons for the narrow scope:</p>
<ul>
<li>Working groups need focused charters, especially when bringing a new protocol to the IETF</li>
<li>The repo and sync protocol is the most foundational part of AT and is therefore the most impactful to have under neutral governance</li>
<li>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)</li>
</ul>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-ietf">Why IETF?<a href="https://docs.bsky.app/blog/taking-at-to-ietf#why-ietf" class="hash-link" aria-label="Direct link to Why IETF?" title="Direct link to Why IETF?">​</a></h2>
<p>This is part of an ongoing effort to mature the governance of AT. (See also: the parallel work that we’re pushing forward on <a href="https://docs.bsky.app/blog/plc-directory-org" target="_blank" rel="noopener noreferrer">moving PLC to an independent organization</a>)</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-you-can-do">What You Can Do<a href="https://docs.bsky.app/blog/taking-at-to-ietf#what-you-can-do" class="hash-link" aria-label="Direct link to What You Can Do" title="Direct link to What You Can Do">​</a></h2>
<p>Read the drafts! Take a look at what we’ve submitted and see what you think. We have a <a href="https://github.com/bluesky-social/ietf-drafts" target="_blank" rel="noopener noreferrer">public GitHub repo</a> 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.</p>
<p>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.</p>]]></content>
        <category label="updates" term="updates"/>
        <category label="ietf" term="ietf"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Creating an Independent Public Ledger of Credentials (PLC) Directory Organization]]></title>
        <id>https://docs.bsky.app/blog/plc-directory-org</id>
        <link href="https://docs.bsky.app/blog/plc-directory-org"/>
        <updated>2025-09-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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.]]></summary>
        <content type="html"><![CDATA[<p>The Bluesky Social app is built on an open network protocol that refers to each user by a unique Decentralized Identifier, or DID (<a href="https://www.w3.org/TR/did-1.0/" target="_blank" rel="noopener noreferrer">a W3C standard</a>). The most popular supported DID method was developed in-house by Bluesky Social, and is called "Public Ledger of Credentials", or <a href="https://web.plc.directory/" target="_blank" rel="noopener noreferrer">PLC</a>. 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.</p>
<p>Until now.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>You can read more about the PLC identity system at <a href="https://web.plc.directory/" target="_blank" rel="noopener noreferrer">web.plc.directory</a>, and more about its use in the AT network in the <a href="https://atproto.com/guides/identity" target="_blank" rel="noopener noreferrer">Identity Protocol Documentation</a>.</p>]]></content>
        <category label="plc" term="plc"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[OAuth Improvements]]></title>
        <id>https://docs.bsky.app/blog/oauth-improvements</id>
        <link href="https://docs.bsky.app/blog/oauth-improvements"/>
        <updated>2025-06-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We've been making improvements to the end-user and developer experiences with atproto OAuth, and wanted to share some updates.]]></summary>
        <content type="html"><![CDATA[<p>We've been making improvements to the end-user and developer experiences with atproto OAuth, and wanted to share some updates.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="email-transitional-scope">Email Transitional Scope<a href="https://docs.bsky.app/blog/oauth-improvements#email-transitional-scope" class="hash-link" aria-label="Direct link to Email Transitional Scope" title="Direct link to Email Transitional Scope">​</a></h2>
<p>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, <code>transition:email</code> that clients can request for access to the account's email. The email itself can be accessed via the <code>com.atproto.server.getSession</code> endpoint on the PDS, using an OAuth access token.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="public-versus-confidential-clients">Public versus Confidential Clients<a href="https://docs.bsky.app/blog/oauth-improvements#public-versus-confidential-clients" class="hash-link" aria-label="Direct link to Public versus Confidential Clients" title="Direct link to Public versus Confidential Clients">​</a></h2>
<p>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.</p>
<p>We have a new essay that tries to untangle this knot, explaining the security concerns and trade-offs involved: <a href="https://github.com/bluesky-social/atproto/discussions/3950" target="_blank" rel="noopener noreferrer">"OAuth Client Security in the Atmosphere"</a>.</p>
<p>We also have a proposal to enable a new architecture: <a href="https://github.com/bluesky-social/proposals/tree/main/0010-client-assertion-backend" target="_blank" rel="noopener noreferrer">"Client Assertion Backend for Browser-based Applications"</a>. 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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="session-lifetimes">Session Lifetimes<a href="https://docs.bsky.app/blog/oauth-improvements#session-lifetimes" class="hash-link" aria-label="Direct link to Session Lifetimes" title="Direct link to Session Lifetimes">​</a></h2>
<p>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.</p>
<p>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:</p>
<ul>
<li>Public Clients<!-- -->
<ul>
<li>increase overall session lifetime (after which tokens can not be refreshed) from one week to two weeks</li>
<li>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)</li>
</ul>
</li>
<li>Confidential Clients<!-- -->
<ul>
<li>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)</li>
<li>increase overall session lifetime (assuming tokens are refreshed) from one year to two years; including existing sessions</li>
</ul>
</li>
</ul>
<p>Remember that the maximum lifetime of OAuth <em>access tokens</em> is much shorter: the specification recommends a limit of 30 minutes, and the reference implementation uses 15 minutes.</p>
<p>There is a tracking issue here: <a href="https://github.com/bluesky-social/atproto/pull/3883" target="_blank" rel="noopener noreferrer">bluesky-social/atproto#3883</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="auth-scopes">Auth Scopes<a href="https://docs.bsky.app/blog/oauth-improvements#auth-scopes" class="hash-link" aria-label="Direct link to Auth Scopes" title="Direct link to Auth Scopes">​</a></h2>
<p>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.</p>
<p>You can learn more by reading an earlier <a href="https://github.com/bluesky-social/atproto/discussions/3655" target="_blank" rel="noopener noreferrer">draft of this protocol feature from March 2025</a>.</p>
<p>We will begin server-side implementation and integration work on this feature soon after a final proposal is published.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="other-changes-and-bug-fixes">Other Changes and Bug Fixes<a href="https://docs.bsky.app/blog/oauth-improvements#other-changes-and-bug-fixes" class="hash-link" aria-label="Direct link to Other Changes and Bug Fixes" title="Direct link to Other Changes and Bug Fixes">​</a></h2>
<p>The DPoP JWTs created by the <code>@atproto/oauth-client</code> TypeScript package incorrectly included query parameters as part of the <code>htu</code> request URL field, which goes against the DPoP specification. The client package has been fixed as of version <a href="https://www.npmjs.com/package/@atproto/oauth-client?activeTab=versions" target="_blank" rel="noopener noreferrer">0.3.18</a>.</p>
<p>The Authorization Server used to require a DPoP proof during Pushed Authorization Requests when the parameters contained a <code>dpop_jkt</code>. This was not valid per <a href="https://datatracker.ietf.org/doc/html/rfc9449#section-10.1-2.1" target="_blank" rel="noopener noreferrer">spec</a> and is thus no longer the case.</p>
<p>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 <code>iss</code> field. The example Python code and TypeScript OAuth client implementation also included this field. This is not required by the DPoP specification, and should <em>not</em> be implemented by clients or SDKs. We have updated the guide and implementations to remove this field.</p>
<p>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.</p>
<p>A couple of subtle bugs with the TypeScript OAuth client SDK and DPoP nonce caching have been fixed.</p>
<p>If a client app redirects to the reference PDS without a <code>login_hint</code>, 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.</p>
<p>The client app redirect URL no longer needs to be on the same host origin as the client metadata document.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="deprecation-notice">Deprecation notice<a href="https://docs.bsky.app/blog/oauth-improvements#deprecation-notice" class="hash-link" aria-label="Direct link to Deprecation notice" title="Direct link to Deprecation notice">​</a></h2>
<p>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.</p>
<ul>
<li>
<p>DPoP proofs that contain a query or fragment in the <code>htu</code> claim should be rejected.</p>
</li>
<li>
<p>JWT for the client attestation using the <code>private_key_jwt</code> authentication method <a href="https://www.rfc-editor.org/rfc/rfc7523.html#section-3" target="_blank" rel="noopener noreferrer">MUST</a> contain an <code>exp</code> claim. This is currently not enforced by the reference implementation.</p>
</li>
<li>
<p>When performing a Pushed Authorization Request, the client <a href="https://datatracker.ietf.org/doc/html/rfc9449#section-10.1-2.1" target="_blank" rel="noopener noreferrer">must</a> either provide a <code>dpop_jkt</code> 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.</p>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="remaining-limitations">Remaining Limitations<a href="https://docs.bsky.app/blog/oauth-improvements#remaining-limitations" class="hash-link" aria-label="Direct link to Remaining Limitations" title="Direct link to Remaining Limitations">​</a></h2>
<p>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.</p>
<p>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 <a href="https://github.com/bluesky-social/atproto/pull/3847" target="_blank" rel="noopener noreferrer">https://github.com/bluesky-social/atproto/pull/3847</a>. 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.</p>]]></content>
        <category label="oauth" term="oauth"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Network Account Management]]></title>
        <id>https://docs.bsky.app/blog/account-management</id>
        <link href="https://docs.bsky.app/blog/account-management"/>
        <updated>2025-05-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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.]]></summary>
        <content type="html"><![CDATA[<p>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.</p>
<p>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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="oauth-account-sign-up-flow">OAuth Account Sign-Up Flow<a href="https://docs.bsky.app/blog/account-management#oauth-account-sign-up-flow" class="hash-link" aria-label="Direct link to OAuth Account Sign-Up Flow" title="Direct link to OAuth Account Sign-Up Flow">​</a></h3>
<p>The PDS reference implementation has included a web interface for authorization flows <a href="https://docs.bsky.app/blog/oauth-atproto" target="_blank" rel="noopener noreferrer">since September 2024</a>, 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.</p>
<p>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.</p>
<p><img decoding="async" loading="lazy" alt="Web interface for &amp;quot;pds.example.com&amp;quot;, with buttons &amp;quot;Create a new account&amp;quot;, &amp;quot;Sign in&amp;quot;, and &amp;quot;Cancel&amp;quot;. There is a language-selection drop-down menu in the lower right corner." src="https://docs.bsky.app/assets/images/pds-account-landing-855271214dba1ded0b3f98ae407662c2.png" width="1012" height="656" class="img_ev3q"></p>
<p><em>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.</em></p>
<p><img decoding="async" loading="lazy" alt="&amp;quot;Create Account&amp;quot; web interface, prompting user to &amp;quot;Choose a username&amp;quot; as &amp;quot;Step 1 of 2&amp;quot;" src="https://docs.bsky.app/assets/images/pds-create-account-21dbf24aceda3e940735f126ebe26a69.png" width="1007" height="644" class="img_ev3q"></p>
<p><em>"Create Account" web interface, prompting user to "Choose a username" as "Step 1 of 2"</em></p>
<p>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 <code>PDS_LOGO_URL</code>, <code>PDS_SERVICE_NAME</code>, and other configuration variables. When using Bluesky's hosting service (<code>bsky.social</code>), the flow will indicate that Bluesky (the company) is the hosting provider.</p>
<p>You can experiment with this flow by entering a PDS hostname (or <code>https://bsky.social</code>) using the Python Flask OAuth demo at: <a href="https://oauth-flask.demo.bsky.dev/oauth/login" target="_blank" rel="noopener noreferrer">https://oauth-flask.demo.bsky.dev/oauth/login</a></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="account-management-interface">Account Management Interface<a href="https://docs.bsky.app/blog/account-management#account-management-interface" class="hash-link" aria-label="Direct link to Account Management Interface" title="Direct link to Account Management Interface">​</a></h3>
<p>The reference implementation's account management interface is at the path <code>/account</code>. 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 <code>pds.example.com</code>, the full URL would be <code>https://pds.example.com/account</code>. For an account hosted on Bluesky's hosting service, the URL is <a href="https://bsky.social/account" target="_blank" rel="noopener noreferrer">https://bsky.social/account</a>.</p>
<p><img decoding="async" loading="lazy" alt="Web Sign-In interface, asking for &amp;quot;Identifier&amp;quot; and &amp;quot;Password&amp;quot;. There is a &amp;quot;Forgot Password?&amp;quot; link, and a language selector drop-down." src="https://docs.bsky.app/assets/images/pds-mgmt-signin-3ea4a7925d9fd9033969fa4e0eea3ccd.png" width="1053" height="598" class="img_ev3q"></p>
<p><em>Web Sign-In interface, asking for "Identifier" and "Password". There is a "Forgot Password?" link, and a language selector drop-down.</em></p>
<p>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.</p>
<p><img decoding="async" loading="lazy" alt="Web interface for &amp;quot;Your Account&amp;quot;. Shows a list of &amp;quot;Connected apps&amp;quot; (Statusphere React App and OAuth Flask Backend Demo), with &amp;quot;Revoke access&amp;quot; buttons for each. Also has a list of &amp;quot;My Devices&amp;quot;, showing &amp;quot;Linux - Firefox&amp;quot; and &amp;quot;Linux - Chrome&amp;quot;, the latter indicated as &amp;quot;This device&amp;quot;, both with &amp;quot;Sign Out&amp;quot; buttons." src="https://docs.bsky.app/assets/images/pds-mgmt-sessions-0bef06de591f4b6ff54d70d571564245.png" width="1009" height="807" class="img_ev3q"></p>
<p><em>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.</em></p>
<p>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.</p>
<p>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.</p>
<p>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).</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="other-oauth-progress">Other OAuth Progress<a href="https://docs.bsky.app/blog/account-management#other-oauth-progress" class="hash-link" aria-label="Direct link to Other OAuth Progress" title="Direct link to Other OAuth Progress">​</a></h3>
<p>The protocol team released an initial <a href="https://github.com/bluesky-social/atproto/discussions/3655" target="_blank" rel="noopener noreferrer">Auth Scopes design sketch in March</a>, 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.</p>
<p>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 <a href="https://atproto.com/specs/oauth" target="_blank" rel="noopener noreferrer">atproto OAuth specifications</a>.</p>
<p>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:</p>
<ul>
<li><a href="https://bsky.app/profile/atproto.com" target="_blank" rel="noopener noreferrer">@atproto.com</a> account on Bluesky</li>
<li><a href="https://github.com/bluesky-social/atproto/discussions" target="_blank" rel="noopener noreferrer">Discussions</a> on Github</li>
<li><a href="https://fediversereport.com/" target="_blank" rel="noopener noreferrer">The Fediverse Report</a> includes weekly AT Protocol coverage</li>
<li><a href="https://atprotocol.dev/" target="_blank" rel="noopener noreferrer">atprotocol.dev</a> Working Groups</li>
</ul>]]></content>
        <category label="pds" term="pds"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Relay Updates for Sync v1.1]]></title>
        <id>https://docs.bsky.app/blog/relay-sync-updates</id>
        <link href="https://docs.bsky.app/blog/relay-sync-updates"/>
        <updated>2025-05-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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.]]></summary>
        <content type="html"><![CDATA[<p>We have an updated relay implementation which incorporates Sync v1.1 protocol changes, and are preparing to switch over the <code>bsky.network</code> relay operated by Bluesky. This post describes new infrastructure developers can start using today and describes our plan for completing the migration.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="updated-relay-implementation">Updated Relay Implementation<a href="https://docs.bsky.app/blog/relay-sync-updates#updated-relay-implementation" class="hash-link" aria-label="Direct link to Updated Relay Implementation" title="Direct link to Updated Relay Implementation">​</a></h2>
<p>The new version of the relay is available now in the <a href="https://github.com/bluesky-social/indigo/tree/main/cmd/relay" target="_blank" rel="noopener noreferrer"><code>indigo</code> go git repository</a>, named simply <code>relay</code>. It is a fork and refactor of the <code>bigsky</code> 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 <code>bsky.network</code> with a patched non-archival version of <code>bigsky</code> for some time, but this update includes a number of other changes:</p>
<ul>
<li>the new <code>#sync</code> message type is supported</li>
<li>the deprecated <code>#handle</code>, <code>#migration</code>, and <code>#tombstone</code> message types are fully removed (they were replaced with <code>#identity</code> and <code>#account</code>)</li>
<li>the sync v1.1 changes to the <code>#commit</code> message schema are supported</li>
<li>message validation and data limits updated to align with written specification</li>
<li>account hosting status and lifecycle updated to match written specification</li>
<li><code>#commit</code> messages validated with MST inversion (controlled by "lenient mode" flag)</li>
<li>new <code>com.atproto.sync.listHosts</code> endpoint to enumerate subscribed PDS instances</li>
<li><code>com.atproto.sync.getRepo</code> endpoint implemented as HTTP redirect to PDS instance</li>
<li>simplified configuration, operation, and SQL database schemas</li>
</ul>
<p>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.)</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="staged-rollout">Staged Rollout<a href="https://docs.bsky.app/blog/relay-sync-updates#staged-rollout" class="hash-link" aria-label="Direct link to Staged Rollout" title="Direct link to Staged Rollout">​</a></h2>
<p>The end goal is to upgrade the <code>bsky.network</code> 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.</p>
<p>We have two new production relay instances:</p>
<ul>
<li><code>relay1.us-west.bsky.network</code></li>
<li><code>relay1.us-east.bsky.network</code></li>
</ul>
<p>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 <code>20000000000</code> (20 billion) to distinguish them from the current <code>bsky.network</code> relay, which has a sequence around <code>8400000000</code> (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 <code>rainbow</code> daemons which fan-out the firehose.</p>
<p>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 <code>#commit</code> messages. Bluesky intends to operate both of these relays, at these hostnames, for the foreseeable future.</p>
<p>For the second stage of the roll-out (in the near future), we will update the <code>bsky.network</code> 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.</p>
<p>As a final stage, we will re-configure both instances to enforce MST inversion more strictly and drop <code>#commit</code> 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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="other-sync-v11-progress">Other Sync v1.1 Progress<a href="https://docs.bsky.app/blog/relay-sync-updates#other-sync-v11-progress" class="hash-link" aria-label="Direct link to Other Sync v1.1 Progress" title="Direct link to Other Sync v1.1 Progress">​</a></h2>
<p>We have a few resources for PDS developers implementing Sync v1.1:</p>
<ul>
<li>the <code>goat</code> CLI tool has several <code>--verify</code> flags on firehose consumer command, which can be pointed at a local PDS instance for debugging</li>
<li>likewise the relay implementation can be running locally (eg, using sqlite) and pointed at a local PDS instance</li>
<li>interoperability test data in the <a href="https://github.com/bluesky-social/atproto-interop-tests" target="_blank" rel="noopener noreferrer"><code>bluesky-social/atproto-interop-tests</code></a> git repository, both for MST implementation details, and for commit proofs</li>
<li>the written specifications have not been updated yet, but the <a href="https://github.com/bluesky-social/proposals/tree/main/0006-sync-iteration" target="_blank" rel="noopener noreferrer">proposal doc</a> is thorough and will be the basis for updated specs</li>
</ul>
<p>While not strictly required by the protocol, both of the new relay hostnames support the <code>com.atproto.sync.listReposByCollection</code> endpoint (technically via the <code>collectiondir</code> 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 <code>app.bsky.*</code> namespace.</p>
<p>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 <code>getRepo</code>, which will allow fetching a subset of the repository.</p>]]></content>
        <category label="firehose" term="firehose"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[2025 Protocol Roadmap (Spring and Summer)]]></title>
        <id>https://docs.bsky.app/blog/2025-protocol-roadmap-spring</id>
        <link href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring"/>
        <updated>2025-03-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Join the Github discussion here.]]></summary>
        <content type="html"><![CDATA[<p><em>Join the <a href="https://github.com/bluesky-social/atproto/discussions/3616" target="_blank" rel="noopener noreferrer">Github</a> discussion here.</em></p>
<p>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.</p>
<p>We recently <a href="https://docs.bsky.app/blog/looking-back-2024" target="_blank" rel="noopener noreferrer">summarized progress on the protocol in 2024</a>. This blog post will be forward looking, covering our protocol goals for the next 6-7 months. As a high-level summary:</p>
<ul>
<li>Updates to the relay, firehose, and public repo sync semantics (<a href="https://github.com/bluesky-social/proposals/tree/main/0006-sync-iteration" target="_blank" rel="noopener noreferrer">Sync v1.1</a>) are starting to roll out</li>
<li>Design work on Auth Scopes has started, which will improve atproto OAuth</li>
<li>PDS will get a web interface for generic account management and signup</li>
<li>Shared data (eg, group privacy) will likely be the next major protocol component, with E2EE DMs following that</li>
</ul>
<p>We also have a quick section on deprecated developer patterns; please give those a look!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sync-v11">Sync v1.1<a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring#sync-v11" class="hash-link" aria-label="Direct link to Sync v1.1" title="Direct link to Sync v1.1">​</a></h2>
<p>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 <a href="https://github.com/bluesky-social/proposals/tree/main/0006-sync-iteration" target="_blank" rel="noopener noreferrer">full proposal</a> gets into all the details, but to summarize:</p>
<ul>
<li>Efficient mechanism for validating MST operations in individual repository commits ("inductive firehose")</li>
<li>Adding a new <code>#sync</code> message type, and removing the <code>tooBig</code> flag on commits</li>
<li>New <code>desynchronized</code> and <code>throttled</code> account statuses, to communicate temporary failures</li>
<li>New <code>com.atproto.sync.listReposByCollection</code> endpoint to help with backfill</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="auth-scopes">Auth Scopes<a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring#auth-scopes" class="hash-link" aria-label="Direct link to Auth Scopes" title="Direct link to Auth Scopes">​</a></h2>
<p>We are updating <a href="https://docs.bsky.app/blog/oauth-atproto" target="_blank" rel="noopener noreferrer">OAuth for AT Protocol</a> 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.</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="pds-account-management">PDS Account Management<a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring#pds-account-management" class="hash-link" aria-label="Direct link to PDS Account Management" title="Direct link to PDS Account Management">​</a></h2>
<p>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 <code>com.atproto.*</code> 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.</p>
<p>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.</p>
<p>The details of the web interface will be implementation-specific. Other PDS implementations might provide different functionality, or make different design choices.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="privately-shared-data-and-e2ee-dms">Privately Shared Data and E2EE DMs<a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring#privately-shared-data-and-e2ee-dms" class="hash-link" aria-label="Direct link to Privately Shared Data and E2EE DMs" title="Direct link to Privately Shared Data and E2EE DMs">​</a></h2>
<p>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.</p>
<p>Shared data will depend on Auth Scopes, and we don't expect to start design work until that is complete.</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="deprecations">Deprecations<a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring#deprecations" class="hash-link" aria-label="Direct link to Deprecations" title="Direct link to Deprecations">​</a></h2>
<p>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.</p>
<p>A simple deprecation are the <code>#tombstone</code>, and <code>#handle</code>, and <code>#migrate</code> events on the firehose. These were replaced with <code>#identity</code> and <code>#account</code> early last year (2024), and have been deprecated since then. We will remove them from the atproto Lexicons entirely soon.</p>
<p>Client apps should resolve user login identifiers (handles or DIDs) to PDS instances, and should not hardcode the <code>bsky.social</code> 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.</p>
<p>When making proxied requests to a PDS, clients can specify a remote service to forward to via the <code>atproto-proxy</code> header. To date, the reference PDS implementation has automatically forwarded <code>app.bsky.*</code> endpoints to the Bluesky API server (<code>api.bsky.app</code>). 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 <code>did:web:api.bsky.app#bsky_appview</code>; you can see more example service DIDs in the <a href="https://docs.bsky.app/docs/advanced-guides/api-directory" target="_blank" rel="noopener noreferrer">API Hosts and Auth</a> docs.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="keep-up-with-ecosystem">Keep up with Ecosystem<a href="https://docs.bsky.app/blog/2025-protocol-roadmap-spring#keep-up-with-ecosystem" class="hash-link" aria-label="Direct link to Keep up with Ecosystem" title="Direct link to Keep up with Ecosystem">​</a></h2>
<p>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:</p>
<ul>
<li>Follow the <a href="https://bsky.app/profile/atproto.com" target="_blank" rel="noopener noreferrer">@atproto.com</a> Bluesky account</li>
<li>Contribute to or read <a href="https://github.com/bluesky-social/atproto/discussions" target="_blank" rel="noopener noreferrer">discussions on Github</a></li>
<li>Subscribe to the <a href="https://fediversereport.com/" target="_blank" rel="noopener noreferrer">The Fediverse Report</a>, an independent newsletter that includes weekly AT Protocol coverage</li>
<li>Attend <a href="https://atprotocol.dev/" target="_blank" rel="noopener noreferrer">atprotocol.dev</a>’s independently-run Tech Talks or the <a href="https://atprotocol.dev/atmosphereconf/" target="_blank" rel="noopener noreferrer">ATmosphere Conference</a> this month in Seattle</li>
</ul>]]></content>
        <category label="updates" term="updates"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[@atproto/api v0.14.0 release notes]]></title>
        <id>https://docs.bsky.app/blog/api-v0-14-0-release-notes</id>
        <link href="https://docs.bsky.app/blog/api-v0-14-0-release-notes"/>
        <updated>2025-02-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Today we are excited to announce the availability of version 0.14 of our TypeScript SDK on npm.]]></summary>
        <content type="html"><![CDATA[<p>Today we are excited to announce the availability of version 0.14 of our TypeScript SDK on <a href="https://www.npmjs.com/package/@atproto/api" target="_blank" rel="noopener noreferrer">npm</a>.</p>
<p>This release is a big step forward, significantly improving the type safety of our <code>@atproto/api</code> package. Let’s take a look at the highlights:</p>
<ul>
<li><strong>Lexicon derived interfaces now have an explicitly defined <code>$type</code> property</strong>, allowing proper discrimination of unions.</li>
<li><strong>Lexicon derived <code>is*</code> utility methods no longer unsafely type cast their input</strong>.</li>
<li><strong>Lexicon derived <code>validate*</code> utility methods now return a more precise type</strong>.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="context">Context<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#context" class="hash-link" aria-label="Direct link to Context" title="Direct link to Context">​</a></h2>
<p>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.</p>
<p>An example of such a placeholder exists in the Lexicon definition of a Bluesky post
(<a href="https://github.com/bluesky-social/atproto/blob/5ece8c6aeab9c5c3f51295d93ed6e27c3c6095c2/lexicons/app/bsky/feed/post.json#L5-L64" target="_blank" rel="noopener noreferrer"><code>app.bsky.feed.post</code></a>), which enables posts to have an <code>embed</code> property defined as follows:</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"embed"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"union"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"refs"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"app.bsky.embed.images"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"app.bsky.embed.video"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"app.bsky.embed.external"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"app.bsky.embed.record"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"app.bsky.embed.recordWithMedia"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The type of the <code>embed</code> property is what is called an "open union". It means that the <code>embed</code> field can basically contain anything, although we usually expect it to be one of the known types defined in the <code>refs</code> array of the Lexicon schema (an image, a video, a link, or another post).</p>
<p>Systems consuming Bluesky posts need to be able to determine what type of embed they are dealing with. This is where the <code>$type</code> property comes in. This property allows systems to uniquely determine the Lexicon schema that must be used to interpret the data, and it <strong>must</strong> be provided everywhere a union is expected. For example, a post with a video would look like this:</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Hey, check this out!"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"createdAt"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2021-09-01T12:34:56Z"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"embed"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"$type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"app.bsky.embed.video"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"video"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">/* reference to the video file, omitted for brevity */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Since <code>embed</code> is an open union, it can be used to store anything. For example, a post with a calendar event embed could look like this:</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Hey, check this out!"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"createdAt"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2021-09-01T12:34:56Z"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"embed"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"$type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"com.example.calendar.event"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"eventName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Party at my house"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"eventDate"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2021-09-01T12:34:56Z"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>Only systems that know about the <code>com.example.calendar.event</code> Lexicon can interpret this data. The official Bluesky app will typically only know about the data types defined in the <code>app.bsky</code> lexicons.</p></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="revamped-typescript-interfaces">Revamped TypeScript interfaces<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#revamped-typescript-interfaces" class="hash-link" aria-label="Direct link to Revamped TypeScript interfaces" title="Direct link to Revamped TypeScript interfaces">​</a></h2>
<p>In order to facilitate working with the Bluesky API, we provide TypeScript interfaces generated from the lexicons (using a tool called <a href="https://www.npmjs.com/package/@atproto/lex-cli" target="_blank" rel="noopener noreferrer">lex-cli</a>). These interfaces are made available through the <code>@atproto/api</code> package.</p>
<p>For historical reasons, these generated types were missing the <code>$type</code> property. The interface for the <code>app.bsky.embed.video</code>, for example, used to look like this:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">Main</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> BlobRef</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  captions</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Caption</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  alt</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  aspectRatio</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyEmbedDefs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">AspectRatio</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">k</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Because the <code>$type</code> property is missing from that interface, developers could write invalid code, without getting an error from TypeScript:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyFeedPost </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> myPost</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyFeedPost</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  text</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Hey, check this out!'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'2021-09-01T12:34:56Z'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  embed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Notice how we are missing the `$type` property</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// here. TypeScript did not complain about this.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">/* reference to the video file, omitted for brevity */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Similarly, a Bluesky post’s <code>embed</code> property was <a href="https://github.com/bluesky-social/atproto/blob/5ece8c6aeab9c5c3f51295d93ed6e27c3c6095c2/packages/api/src/client/types/app/bsky/feed/post.ts#L25-L31" target="_blank" rel="noopener noreferrer">previously</a> typed like this:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">Record</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  embed</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AppBskyEmbedVideo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AppBskyEmbedExternal</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AppBskyEmbedRecord</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AppBskyEmbedRecordWithMedia</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">k</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>It was therefore possible to create a post with a completely invalid "video" embed, and still get no error from the type system:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyFeedPost </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> myPost</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyFeedPost</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  text</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Hey, check this out!'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'2021-09-01T12:34:56Z'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// This is an invalid embed, but TypeScript</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// does not complain.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  embed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.video'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">43</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>We have fixed these issues by making the <code>$type</code> property in the generated interfaces explicit. The <code>app.bsky.embed.video</code> interface now looks like this:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">Main</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  $type</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.video'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> BlobRef</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  captions</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Caption</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  alt</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  aspectRatio</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyEmbedDefs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">AspectRatio</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Notice how the <code>$type</code> property is defined as optional (<code>?:</code>) 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 <code>"type": "ref"</code>). In those cases, there will be no ambiguity as to how the data should be interpreted.</p>
<p>For example, a "Bluesky Like" (<code>app.bsky.feed.like</code>) defines the following properties in its schema:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"properties"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"createdAt"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"string"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"format"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"datetime"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"subject"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ref"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"ref"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"com.atproto.repo.strongRef"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>As can be seen, the <code>subject</code> property is defined as a reference to a <code>com.atproto.repo.strongRef</code> object. In this case, there is no ambiguity as to how the <code>subject</code> of a like should be interpreted, and the <code>$type</code> property is not needed.</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> like</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyFeedLike</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Record </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.feed.like'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  createdAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'2021-09-01T12:34:56Z'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  subject</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// No `$type` property needed here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    uri</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'at://did:plc:123/app.bsky.feed.post/456'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cid</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'[...]'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Because the <code>$type</code> 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 <code>$Typed</code> utility allows marking an interface’s <code>$type</code> property non-optional in contexts where it is required:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">$Typed</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">V</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">V</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The <code>embed</code> property of posts is now defined as follows:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">Record</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  embed</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> $Typed</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> $Typed</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">AppBskyEmbedVideo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> $Typed</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">AppBskyEmbedExternal</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> $Typed</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">AppBskyEmbedRecord</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> $Typed</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">AppBskyEmbedRecordWithMedia</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In addition to preventing the <em>creation</em> of invalid data as seen before, this change also allows properly discriminating types when <em>accessing</em> the data. For example, one can now do:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyFeedPost </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Say we got some random post somehow (typically</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// via an API call)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> post</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyFeedPost</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// And we want to know what kind of embed it contains</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> embed </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> post</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// We can now use the `$type` property to disambiguate</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">embed</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">$type </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.images'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// The `embed` variable is fully typed as</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// `$Typed&lt;AppBskyEmbedImages.Main&gt;` here!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="type-property-in-record-definitions"><code>$type</code> property in <code>record</code> definitions<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#type-property-in-record-definitions" class="hash-link" aria-label="Direct link to type-property-in-record-definitions" title="Direct link to type-property-in-record-definitions">​</a></h3>
<p>While optional in interfaces generated from Lexicon <code>object</code> definitions, the <code>$type</code> property is <strong>required</strong> in interfaces generated from Lexicon <code>record</code> definitions.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="is-utility-methods"><code>is*</code> utility methods<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#is-utility-methods" class="hash-link" aria-label="Direct link to is-utility-methods" title="Direct link to is-utility-methods">​</a></h3>
<p>The example above shows how data can be discriminated based on the <code>$type</code> property. The SDK provides utility methods to perform this kind of discrimination. These methods are named <code>is*</code> and are generated from the lexicons. For example, the <code>app.bsky.embed.images</code> Lexicon used to generate the following <code>isMain</code> utility method:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">Main</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  images</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Image</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">x</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">isMain</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">is</span><span class="token plain"> Main </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    value </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'object'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">'$type'</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$type </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.images'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      value</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">$type </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.images#main'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>That implementation of the discriminator is invalid.</p>
<ul>
<li>First, because a <code>$type</code> is not allowed to end with <code>#main</code> (<a href="https://atproto.com/specs/lexicon#lexicon-files" target="_blank" rel="noopener noreferrer">as per AT Protocol specification</a>).</li>
<li>Second, because the <code>isMain</code> function does not actually check the structure of the object, only its <code>$type</code> property.</li>
</ul>
<p>This invalid behavior could yield runtime errors that could otherwise have been avoided during development:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyEmbedImages </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Get an invalid embed somehow</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> invalidEmbed </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.images'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// notice how the `images` property is missing here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// This predicate function only checks the value of</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// the `$type` property, making the condition "true" here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isMain</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">invalidEmbed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// However, the `images` property is missing here.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// TypeScript does not complain about this, but the</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// following line will throw a runtime error:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'First image:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> invalidEmbed</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">images</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The root of the issue here is that the <code>is*</code> utility methods perform type casting of objects solely based on the value of their <code>$type</code> property. There were basically two ways we could fix this behavior:</p>
<ol>
<li>Alter the implementation to actually validate the object's structure. This would be a non-breaking change that has a negative impact on performance.</li>
<li>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.</li>
</ol>
<p>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.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>In many cases where data needs to be discriminated, this change in the signature of the <code>is*</code> function won't actually cause any issues when upgrading the version of the SDK.</p></div></div>
<p>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, <strong>the data returned by Bluesky's server APIs is "guaranteed" to be valid.</strong> In these cases, the <code>is*</code> utility methods provide a convenient way to discriminate between valid values.</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyEmbedImages </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Get a post from the API (the API's contract</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// guarantees the validity of the data)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> post</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// The `is*` utilities are an efficient way to</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// discriminate **valid** data based on their `$type`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">isImages</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">embed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// `post.embed` is fully typed as</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// `$Typed&lt;AppBskyEmbedImages.Main&gt;` here!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="validate-utility-methods"><code>validate*</code> utility methods<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#validate-utility-methods" class="hash-link" aria-label="Direct link to validate-utility-methods" title="Direct link to validate-utility-methods">​</a></h3>
<p>As part of this update, the signature of the <code>validate*</code> utility methods was updated to properly describe the type of the <code>value</code> in case of success:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyEmbedImages </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Aliased for clarity</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> Images </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> validateImages </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">validateMain</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Get some data somehow</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Validate the data against a particular schema (images here)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">validateImages</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">success</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// The `value` property was previously typed as `unknown`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// and is now properly typed as `Image`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> images </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>These methods perform data validation, making them somewhat slower than the <code>is*</code> utility methods. They can, however, be used in place of the <code>is*</code> utilities when migrating to this new version of the SDK.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="new-aspredicate-function">New <code>asPredicate</code> function<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#new-aspredicate-function" class="hash-link" aria-label="Direct link to new-aspredicate-function" title="Direct link to new-aspredicate-function">​</a></h3>
<p>The SDK exposes a new <code>asPredicate</code> function. This function allows converting a <code>validate*</code> function into a predicate function. This can be useful when working with libraries that expect a predicate function to be passed as an argument.</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> asPredicate</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> AppBskyEmbedImages </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Aliased for clarity</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> Images </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> isValidImages </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">asPredicate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">AppBskyEmbedImages</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">validateMain</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Get an embed with unknown validity somehow</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> embed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// The following condition will be true if, and only</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// if, the value matches the `Image` interface.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">isValidImages</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">embed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// `embed` is of type `Images` here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Similarly, the type predicate can be used to</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// infer the type of an array of unknown values:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> someArray</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// This will be typed as `Images[]`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> images </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> someArray</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isValidImages</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>We decided to introduce the <code>asPredicate</code> function to provide an explicit way to convert <code>validate*</code> functions into predicate functions. More importantly, this function allowed us limit the bundle size increase that would have been caused by the introduction new <code>isValid*</code> utility methods as part of this release.</p></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="removal-of-the-x-string-index-signature">Removal of the <code>[x: string]</code> index signature<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#removal-of-the-x-string-index-signature" class="hash-link" aria-label="Direct link to removal-of-the-x-string-index-signature" title="Direct link to removal-of-the-x-string-index-signature">​</a></h2>
<p>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 <code>[k: string]: unknown</code> index signature in generated interfaces. This is how the video embed used to be represented:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">Main</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> BlobRef</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  captions</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Caption</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  alt</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  aspectRatio</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyEmbedDefs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">AspectRatio</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">k</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This signature allowed for undetectable mistakes to be performed:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyEmbedVideo </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> embed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyEmbedVideo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.video'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">/* omitted */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Notice the typo in `alt`, not resulting in a TypeScript error</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  atl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'My video'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>We removed that signature, requiring any unspecified fields intentionally added to be now explicitly marked as such:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppBskyEmbedVideo </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> embed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AppBskyEmbedVideo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Main </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  $type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'app.bsky.embed.video'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  video</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">/* omitted */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Next line will result in the following</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">//  TypeScript error: "Object literal may only</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// specify known properties, and 'atl' does not</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// exist in type 'Main'"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  atl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'My video'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Unspecified fields must now be explicitly</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// marked as such:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// @ts-expect-error - custom field</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  comExampleCustomProp</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'custom value'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="other-considerations">Other considerations<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#other-considerations" class="hash-link" aria-label="Direct link to Other considerations" title="Direct link to Other considerations">​</a></h2>
<p>When upgrading, please make sure that your project does not depend on multiple versions of the <code>@atproto/*</code> packages. Use <a href="https://classic.yarnpkg.com/en/docs/selective-version-resolutions/" target="_blank" rel="noopener noreferrer">resolutions</a> or <a href="https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides" target="_blank" rel="noopener noreferrer">overrides</a> in your <code>package.json</code> to pin the dependencies to the same version.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="recap">Recap<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#recap" class="hash-link" aria-label="Direct link to Recap" title="Direct link to Recap">​</a></h2>
<p>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.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="migration-tldr">Migration TL;DR:<a href="https://docs.bsky.app/blog/api-v0-14-0-release-notes#migration-tldr" class="hash-link" aria-label="Direct link to Migration TL;DR:" title="Direct link to Migration TL;DR:">​</a></h3>
<ul>
<li>Need to be absolutely sure of your data? Use <code>asPredicate</code> or <code>validate*</code> utilities.</li>
<li>Using data from the Bluesky app view? You can use <code>is*</code> utilities.</li>
<li>Building lex objects for writing? Make sure you use <code>$Typed</code> when building those.</li>
</ul>
<p>Happy coding!</p>]]></content>
        <category label="updates" term="updates"/>
        <category label="guide" term="guide"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Looking Back At 2024 AT Protocol Development]]></title>
        <id>https://docs.bsky.app/blog/looking-back-2024</id>
        <link href="https://docs.bsky.app/blog/looking-back-2024"/>
        <updated>2025-01-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[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.]]></summary>
        <content type="html"><![CDATA[<p>In May 2024, we published a 2024 <a href="https://docs.bsky.app/blog/2024-protocol-roadmap" target="_blank" rel="noopener noreferrer">Protocol Roadmap</a>, and we want to give an end-of-year update. We will follow up soon with a forward-looking roadmap for 2025.</p>
<p>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.</p>
<p>A lot of progress was made in 2024! Some large protocol milestones include:</p>
<ul>
<li><a href="https://bsky.social/about/blog/02-22-2024-open-social-web" target="_blank" rel="noopener noreferrer"><strong>Open PDS federation in the live network</strong>:</a> 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.</li>
<li><a href="https://docs.bsky.app/blog/blueskys-moderation-architecture" target="_blank" rel="noopener noreferrer"><strong>Labeling</strong>:</a> We launched the stackable moderation system with labels and reports.</li>
<li><strong>Account Migration:</strong> Users can migrate their account to alternate PDSes using command-line tooling.</li>
<li><strong>Generic Service Proxying</strong>: Client XRPC requests to the PDS can be proxied on to arbitrary service providers, using inter-service auth, via a client-controlled HTTP header.</li>
<li><strong>Flexible Record Schemas</strong>: Data records of any schema can be written to atproto repositories, with "eager" Lexicon validation controlled by query parameter.</li>
<li><strong>Account Deactivation:</strong> This was implemented at the protocol layer, along with an overhaul of the <code>#identity</code> and <code>#account</code> firehose events.</li>
<li><a href="https://docs.bsky.app/blog/oauth-atproto" target="_blank" rel="noopener noreferrer"><strong>Initial OAuth Support</strong></a>: This was launched in production, with extensive documentation.</li>
<li><a href="https://docs.bsky.app/blog/jetstream" target="_blank" rel="noopener noreferrer"><strong>Jetstream</strong></a>: 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.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="lexicon-resolution">Lexicon Resolution<a href="https://docs.bsky.app/blog/looking-back-2024#lexicon-resolution" class="hash-link" aria-label="Direct link to Lexicon Resolution" title="Direct link to Lexicon Resolution">​</a></h2>
<p>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 <a href="https://github.com/bluesky-social/atproto/discussions/3074" target="_blank" rel="noopener noreferrer">public proposal</a>, 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.</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="auth-and-oauth">Auth and OAuth<a href="https://docs.bsky.app/blog/looking-back-2024#auth-and-oauth" class="hash-link" aria-label="Direct link to Auth and OAuth" title="Direct link to Auth and OAuth">​</a></h2>
<p>The OAuth <a href="https://docs.bsky.app/blog/oauth-atproto" target="_blank" rel="noopener noreferrer">announcement blog post</a> 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.</p>
<p>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.</p>
<p>Based on the <a href="https://github.com/bluesky-social/atproto/discussions/2656" target="_blank" rel="noopener noreferrer">OAuth Roadmap</a>, we are still in the first phase. Apart from Scopes, we have feedback from developers that token lifetimes may need tuning.</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sync-firehose-and-backfill">Sync, Firehose, and Backfill<a href="https://docs.bsky.app/blog/looking-back-2024#sync-firehose-and-backfill" class="hash-link" aria-label="Direct link to Sync, Firehose, and Backfill" title="Direct link to Sync, Firehose, and Backfill">​</a></h2>
<p>As the overall network has scaled to over 26 million accounts, resource costs around Relays and the firehose mechanism have become more urgent.</p>
<p>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 <a href="https://whtwnd.com/bnewbold.net/entries/Notes%20on%20Running%20a%20Full-Network%20atproto%20Relay%20(July%202024)" target="_blank" rel="noopener noreferrer">relatively affordable</a>, and further scaling could be achieved with infrastructure sharding.</p>
<p>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 <code>tooBig</code> mechanism for large commits, and clarify what a downstream service needs to do to synchronize data reliably.</p>
<p>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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="specifications-and-ecosystem">Specifications and Ecosystem<a href="https://docs.bsky.app/blog/looking-back-2024#specifications-and-ecosystem" class="hash-link" aria-label="Direct link to Specifications and Ecosystem" title="Direct link to Specifications and Ecosystem">​</a></h2>
<p>We refreshed the <a href="https://atproto.com/" target="_blank" rel="noopener noreferrer">AT Protocol website</a>, and the written specifications were expanded to cover the Firehose, Blobs, Account Hosting, and more.</p>
<p>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.</p>
<p><a href="https://dasl.ing/" target="_blank" rel="noopener noreferrer">DASL</a> 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.</p>
<p><a href="https://lexicon.community/" target="_blank" rel="noopener noreferrer">lexicon.community</a> 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.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-else">What Else?<a href="https://docs.bsky.app/blog/looking-back-2024#what-else" class="hash-link" aria-label="Direct link to What Else?" title="Direct link to What Else?">​</a></h2>
<p>A few important areas did not see much change in 2024. The PLC identity system has been operating reliably, but the <code>plc.directory</code> 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.</p>
<p>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.</p>
<p>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:</p>
<ul>
<li><a href="https://bsky.app/profile/atproto.com" target="_blank" rel="noopener noreferrer">@atproto.com</a> account on Bluesky</li>
<li><a href="https://github.com/bluesky-social/atproto/discussions" target="_blank" rel="noopener noreferrer">Discussions</a> on Github</li>
<li><a href="https://fediversereport.com/" target="_blank" rel="noopener noreferrer">The Fediverse Report</a> includes weekly AT Protocol coverage</li>
<li><a href="https://atprotocol.dev/" target="_blank" rel="noopener noreferrer">atprotocol.dev</a> runs Tech Talks</li>
</ul>]]></content>
        <category label="updates" term="updates"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Relay Operational Updates]]></title>
        <id>https://docs.bsky.app/blog/relay-ops</id>
        <link href="https://docs.bsky.app/blog/relay-ops"/>
        <updated>2024-11-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[**Update May 2025:** This post has been superceded by the Relay Updates for Sync v1.1 post.]]></summary>
        <content type="html"><![CDATA[<p><em><strong>Update May 2025:</strong> This post has been superceded by the <a href="https://docs.bsky.app/blog/relay-sync-updates">Relay Updates for Sync v1.1</a> post.</em></p>
<p><em><strong>Update January 2025:</strong> Many of the operational changes described here were not implemented. We will give an update on Relay infrastructure early this year.</em></p>
<p><strong>Summary:</strong> We are making a couple of changes to the Bluesky relay servers <strong>this week</strong>. The wire protocol and semantics are not impacted, but the changes may impact firehose consumers:</p>
<ul>
<li>One way or another, the event stream sequence will change for all subscribers. Most consumers can probably just reset to the new cursor; see details below.</li>
<li>The <code>bsky.network</code> hostname will be swapped to a new relay instance (with new sequence)</li>
</ul>
<p>The atproto network is growing rapidly! This is exciting and positive, but it means we need to move up some of our network scaling plans. One particular resource bottleneck is the relay firehose. We are seeing sustained traffic of over 2,000 events per second, and we have hundreds of active consumers. Multiplied, the overall throughput is hard for any one server to keep up with.</p>
<p>We saw this problem coming from the beginning and we have several plans and options to mitigate it. Details for each of these are linked or in sections below.</p>
<p>First, we are simply upgrading the size of server that we run our primary relay instances on. We expect to do this in coming days. The main impact here will be a reset of the firehose sequence (including cursor values), which will impact all downstream consumers.</p>
<p>Second, we are introducing the concept of "firehose fan-out" services, and releasing our implementation <a href="https://github.com/bluesky-social/indigo/tree/main/cmd/rainbow" target="_blank" rel="noopener noreferrer">Rainbow</a>. These are servers which re-broadcast the event stream firehose to many clients, reducing the bandwidth load on relays themselves. Rainbow is now in production, as an implementation detail.</p>
<p>Third, we recently released <a href="https://docs.bsky.app/blog/jetstream" target="_blank" rel="noopener noreferrer">Jetstream</a> as a more informal and light-weight option for event stream consumption. We encourage developers to check out Jetstream, and switch over if it works for their use case.</p>
<p>We encourage folks to take advantage of Rainbow and Jetstream. You can run your own, or make use of our hosted instances. Some additional longer-term options are discussed at the bottom of this article.</p>
<p>We don't have a hard time or date estimate for the cursor sequence change, but we expect it this week, possibly tomorrow (Tuesday). We generally try to give more time and make changes less disruptive but we're not able to do so in this situation. Please follow the <a href="https://bsky.app/profile/atproto.com" target="_blank" rel="noopener noreferrer"><code>@atproto.com</code></a> account, this blog, and in <a href="https://github.com/bluesky-social/atproto/discussions" target="_blank" rel="noopener noreferrer">Github Discussions</a> for announcements.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="relay-upgrade">Relay Upgrade<a href="https://docs.bsky.app/blog/relay-ops#relay-upgrade" class="hash-link" aria-label="Direct link to Relay Upgrade" title="Direct link to Relay Upgrade">​</a></h2>
<p>The current Bluesky relay instance is available at <code>wss://bsky.network</code>. A more specific hostname for this relay instance will soon be <code>relay1.us-west.bsky.network</code> (not yet configured).</p>
<p>Instead of upgrading the relay in-place, we are going to start a new relay from scratch. This means that event stream ordering will be different and sequence numbers will not align with the current relay. This new instance will soon run at <code>relay2.us-west.bsky.network</code> (not yet configured).</p>
<p>There will be at least a short window when both relays are running, so downstream consumers can update their services to "cut over". At some point soon after the new relay is deployed, we will switch the generic <code>bsky.network</code> hostname to point at it.</p>
<p>What firehose consumers need to do about the cut over depends on how careful they need to be about processing every single event on the firehose.</p>
<p>Services which always connect to the firehose from the current offset don’t need to do anything. This includes developer tools like <code>goat</code>, and real-time sampling like Firesky.</p>
<p>Best-effort consumers can likely be configured to consume from the current firehose offset, and simply restart, missing at most a few seconds of events. This probably makes sense for feed generators, analytics, automated labeling, etc.</p>
<p>Services which want to really minimize the number of missed events can take a different approach. They can be programmed to connect to the new relay with no cursor, detect the current sequence number, then reconnect with a cursor a couple of minutes back (at current rates this means a few hundred thousand events back). This results in a time overlap window between the two relays (with some events processed twice), which helps minimize the chance of missing any events.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="firehose-fan-out-and-rainbow">Firehose Fan-out and Rainbow<a href="https://docs.bsky.app/blog/relay-ops#firehose-fan-out-and-rainbow" class="hash-link" aria-label="Direct link to Firehose Fan-out and Rainbow" title="Direct link to Firehose Fan-out and Rainbow">​</a></h2>
<p>Firehose fan-out servers have a single upstream connection to a relay firehose, and re-broadcast that stream to multiple subscribing clients. They maintain a local backfill window (allowing re-connection with a cursor), but do not implement endpoints like <code>getRecord</code> or <code>getRepo</code>, only <code>com.atproto.sync.subscribeRepos</code>. They do not re-validate the event stream (eg, they don't verify signatures). They maintain the exact sequence numbers of the upstream relay, meaning that they are interchangeable with the relay and with sibling fan-out servers, and can be placed behind a load-balancer.</p>
<p>Today we are releasing Rainbow, a fan-out service implemented in Go. The <a href="https://github.com/bluesky-social/indigo/tree/main/cmd/rainbow" target="_blank" rel="noopener noreferrer">source code is available</a> in the <code>indigo</code> git repository, with the same MIT/Apache licensing as our other open source projects.</p>
<p>Rainbow is already running as part of our production relay deployment (<code>wss://bsky.network</code>), with our load balancer (<code>haproxy</code>) distributing WebSocket subscriptions to a separate server running Rainbow.</p>
<p>In the future, we may run additional Rainbow instances outside our primary data centers, similar to how we offer Jetstream instances today. This distributes network traffic over more routers, and could improve connection reliability and throughput for subscribers in regions outside North America.</p>
<p>Going forward, we recommend that software which consumes from firehoses (including atproto SDKs) support HTTP redirects for WebSocket connections. This enables "pooling" behavior where a single hostname could route clients to multiple distinct servers, without use of a load-balancer.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-else">What Else?<a href="https://docs.bsky.app/blog/relay-ops#what-else" class="hash-link" aria-label="Direct link to What Else?" title="Direct link to What Else?">​</a></h2>
<p>As a reminder, the relay synchronization API is the same as the PDS synchronization API, including the firehose. Relays are essential to the functionality of the atproto network: they are an operational and developer convenience, allowing consuming services to skip keeping track of which PDS instances are active.</p>
<p>A classic way to scale services like the firehose is <em>sharding</em>, where the stream is split into multiple parallel streams. atproto has a natural sharding key (the account DID), which means related events can be consistently routed to the correct shard. This will allow near-indefinite scaling of the firehose event rate, assuming compute and network resources are available.</p>
<p>Part of what makes relays difficult to operate at scale is that they function as both a "relay" (rebroadcasting events from PDS instances) and a full-network mirror (storing all repo contents). In the current protocol, it is necessary to combine both functions to fully verify repository operations, especially deletion of events. We think that clever implementations could make this work less resource intensive (for example, storing just record CIDs instead of full data). It is also possible to validate <em>most</em> aspects of the event stream without a full copy of the repo tree, and there could be a role in the network for "non-mirroring" relays.</p>
<p>Lastly, there is a particular need for efficient consumption and backfill of full-network content for only content and accounts which make use of specific record types. For example, “only <a href="https://whtwnd.com/" target="_blank" rel="noopener noreferrer">whtwnd</a> blog post records”, or “only accounts with labeling service declarations”. We are planning new features to make this type of network subscription and backfill much more efficient.</p>]]></content>
        <category label="firehose" term="firehose"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Introducing Jetstream]]></title>
        <id>https://docs.bsky.app/blog/jetstream</id>
        <link href="https://docs.bsky.app/blog/jetstream"/>
        <updated>2024-10-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[One of most popular aspects of atproto for developers is the firehose: an aggregated stream of all the public data updates in the network. Independent developers have used the firehose to build real-time monitoring tools (like Firesky), feed generators, labeling services, bots, entire applications, and more.]]></summary>
        <content type="html"><![CDATA[<p>One of most popular aspects of atproto for developers is the firehose: an aggregated stream of all the public data updates in the network. Independent developers have used the firehose to build real-time monitoring tools (like <a href="https://firesky.tv/" target="_blank" rel="noopener noreferrer">Firesky</a>), feed generators, labeling services, bots, entire applications, and more.</p>
<p>But the firehose wire format is also one of the more complex parts of atproto, involving decoding binary CBOR data and CAR files, which can be off-putting to new developers. Additionally, the volume of data has increased rapidly as the network has grown, consistently producing hundreds of events per second.</p>
<p>The full synchronization firehose is core network infrastructure and not going anywhere, but to address these concerns we developed an alternative streaming solution, Jetstream, which has a few key advantages:</p>
<ul>
<li>simple JSON encoding</li>
<li>reduced bandwidth, and compression</li>
<li>ability to filter by collection (NSID) or repo (DID)</li>
</ul>
<p>A Jetstream server consumes from the firehose and fans out to many subscribers. It is <a href="https://github.com/bluesky-social/jetstream" target="_blank" rel="noopener noreferrer">open source</a>, implemented in Go, simple to self-host. There is an official client library included (in Go), and <a href="https://skyware.js.org/guides/jetstream/introduction/getting-started/" target="_blank" rel="noopener noreferrer">community client libraries</a> have been developed.</p>
<p>Jetstream was originally written as a side project by one of our engineers, <a href="https://bsky.app/profile/jaz.bsky.social" target="_blank" rel="noopener noreferrer">Jaz</a>. You can read more about their design goals and efficiency gains <a href="https://jazco.dev/2024/09/24/jetstream" target="_blank" rel="noopener noreferrer">on their blog</a>. It has been successful enough that we are promoting it to a team-maintained project, and are running several public instances:</p>
<ul>
<li><code>jetstream1.us-east.bsky.network</code></li>
<li><code>jetstream2.us-east.bsky.network</code></li>
<li><code>jetstream1.us-west.bsky.network</code></li>
<li><code>jetstream2.us-west.bsky.network</code></li>
</ul>
<p>You can read more technical details about Jetstream in the <a href="https://github.com/bluesky-social/jetstream" target="_blank" rel="noopener noreferrer">Github repo</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-now">Why Now?<a href="https://docs.bsky.app/blog/jetstream#why-now" class="hash-link" aria-label="Direct link to Why Now?" title="Direct link to Why Now?">​</a></h2>
<p>Why are we promoting Jetstream at this time?</p>
<p>Two  factors came to a head in early September: we released an example project for building new applications on atproto (<a href="https://atproto.com/guides/applications" target="_blank" rel="noopener noreferrer">Statusphere</a>), and we had an unexpectedly large surge in traffic in Brazil. Suddenly we had a situation where new developers would be subscribing to a torrential full-network firehose (over a thousand events per second), just to pluck out a handful of individual events from a handful of accounts. Everything about this continued to function, even on a laptop on a WiFi connection, but it feels a bit wild as an introduction to the protocol.</p>
<p>We knew from early on that while the current firehose is extremely powerful, it was not well-suited to some use cases. Until recently, it hadn’t been a priority to develop alternatives. The firehose is a bit overpowered, but it does Just Work.</p>
<p>Has the Relay encountered scaling problems or become unaffordable to operate?</p>
<p>Nope! The current Relay implementation ('bigsky', written in Go, in the indigo git repo) absorbed a 10x surge in daily event rate, with over 200 active subscribers, and continues to chug along reliably. We have <a href="https://whtwnd.com/bnewbold.net/entries/Notes%20on%20Running%20a%20Full-Network%20atproto%20Relay%20(July%202024)" target="_blank" rel="noopener noreferrer">demonstrated</a> how even a full-network Relay can be operated affordably.</p>
<p>We do expect to refactor our Relay implementation and make changes to the firehose wire format to support sharding. But the overall network architecture was designed to support global scale and millions of events per second, and we don't see any serious barriers to reaching that size. Bandwidth costs are manageable today. At larger network size (events times subscribers), bandwidth will grow in cost. We expect that the economic value of the network will provide funding and aligned incentives to cover the operation of core network infrastructure, including Relays. In practical terms, we expect funded projects and organizations depending on the firehose to pay infrastructure providers to ensure reliable operation (eg, an SLA), or to operate their own Relay instances.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="tradeoffs-and-use-cases">Tradeoffs and Use Cases<a href="https://docs.bsky.app/blog/jetstream#tradeoffs-and-use-cases" class="hash-link" aria-label="Direct link to Tradeoffs and Use Cases" title="Direct link to Tradeoffs and Use Cases">​</a></h2>
<p>Jetstream has efficiency and simplicity advantages, but they come with some tradeoffs. We think it is a pragmatic option for many projects, but that developers need to understand what they are getting into.</p>
<p>Events do not include cryptographic signatures or Merkle tree nodes, meaning the data is not self-authenticating. "Authenticated Transfer" is right in the AT Protocol acronym, so this is a pretty big deal! The trust relationship between a Jetstream operator and a consuming client is pretty different from that of a Relay. Not all deployment scenarios and use-cases require verification, and we suspect many projects are already skipping that aspect when consuming from the firehose. If you are running Jetstream locally, or have a tight trust relationship with a service provider, these may be acceptable tradeoffs.</p>
<p>Unlike the firehose (aka, Repository Event Stream), Jetstream is not formally part of the protocol. We are not as committed to maintaining it as a stable API or critical piece of infrastructure long-term, and we anticipate adopting some of the advantages it provides into the protocol firehose over time.</p>
<p>On the plus side, Jetstream is easier and cheaper to operate than a Relay instance. Folks relying on Jetstream can always run their own copy on their own servers.</p>
<p>Some of the use cases we think Jetstream is a good fit for:</p>
<ul>
<li>casual, low-stakes projects and social toys: interactive bots, and "fun" badging labelers (eg, <a href="https://bsky.app/profile/kiki-bouba.mozzius.dev" target="_blank" rel="noopener noreferrer">Kiki/Bouba</a>)</li>
<li>experimentation and prototyping: student projects, proofs of concept, demos</li>
<li>informal metrics and visualizations</li>
<li>developing new applications: filtering by collection is particularly helpful when working with new Lexicons and debugging</li>
<li>internal systems: if you have multiple services consuming from the firehose, a single local Jetstream instance can be used to fan out to multiple subscribers</li>
</ul>
<p>Some projects it is probably not the right tool for:</p>
<ul>
<li>mirroring, backups, and archives</li>
<li>any time it is important to know "who said what"</li>
<li>moderation or anti-abuse actions</li>
<li>research studies</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-else">What Else?<a href="https://docs.bsky.app/blog/jetstream#what-else" class="hash-link" aria-label="Direct link to What Else?" title="Direct link to What Else?">​</a></h2>
<p>The ergonomics of working with the firehose and "backfilling" bulk data from the network are something we would like to improve in the protocol itself. This might include mechanisms for doing "selective sync" of specific collections within a repo, while still getting full verification of authenticity.</p>
<p>It would be helpful to have a mechanism to identify which repos in the network have any records of a specific type, without inspecting every account individually. For example, enumerating all of the labelers or feed generators in the network. This is particularly important for new applications with a small initial user base.</p>
<p>We are working to complete the atproto specifications for the firehose and for account hosting status.</p>]]></content>
        <category label="firehose" term="firehose"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Lexicons, Pinned Posts, and Interoperability]]></title>
        <id>https://docs.bsky.app/blog/pinned-posts</id>
        <link href="https://docs.bsky.app/blog/pinned-posts"/>
        <updated>2024-10-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[As the AT Protocol matures, developers are building alternative Bluesky clients and entirely novel applications with independent Lexicions. We love to see it! This is very aligned with our vision for the ATmosphere, and we intend to encourage more of this through additional developer documentation and tooling.]]></summary>
        <content type="html"><![CDATA[<p>As the AT Protocol matures, developers are building alternative Bluesky clients and entirely novel applications with independent Lexicions. We love to see it! This is very aligned with our vision for the ATmosphere, and we intend to encourage more of this through additional developer documentation and tooling.</p>
<p>One of the major components of the protocol is the concept of "Lexicons," which are machine-readable schemas for both API endpoints and data records. The goal with Lexicons is to make it possible for independent projects to work with the same data types reliably. Users should be able to choose which software they use to interact with the network, and it is important that developers are able to call shared APIs and write shared data records with confidence.</p>
<p>While the Lexicon concept has been baked into the protocol from the beginning, some aspects are still being finalized, and best practices around extensions, collaboration, and governance are still being explored.</p>
<p>A recent incident in the live network brought many of these abstract threads into focus. Because norms and precedent are still being established, we thought it would be good to dig into the specific situation and give some updates.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-happened">What Happened?<a href="https://docs.bsky.app/blog/pinned-posts#what-happened" class="hash-link" aria-label="Direct link to What Happened?" title="Direct link to What Happened?">​</a></h3>
<p>On October 10, Bluesky released version 1.92 of our main app. This release added support for "pinned posts," a long-requested feature. This update added a <code>pinnedPost</code> field to the <code>app.bsky.actor.profile</code> record. This <a href="https://github.com/bluesky-social/atproto/blob/2676206e422233fefbf2d9d182e8d462f0957c93/lexicons/app/bsky/actor/profile.json#L44" target="_blank" rel="noopener noreferrer">field is declared</a> as a <code>com.atproto.repo.strongRef</code>, which is an object containing both the URL and a hash (CID) of the referenced data record.</p>
<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l66vf2q4pi26" data-bluesky-cid="bafyreibonvobqqubi5aklhvd3t56utmp6yf5c5riifd4lobowdwi6sayei"><p lang="en">📢 App Version 1.92 is rolling out now (1/5)Pinned posts are here! Plus lots of UI improvements, including new font options, and the ability to filter your searches by language.Open this thread for more details. 🧵<br><br><a href="https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur/post/3l66vf2q4pi26?ref_src=embed">[image or embed]</a></p>— Bluesky (<a href="https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur?ref_src=embed">@bsky.app</a>) <a href="https://bsky.app/profile/did:plc:z72i7hdynmk6r22z27h6tvur/post/3l66vf2q4pi26?ref_src=embed">Oct 10, 2024 at 3:24 PM</a></blockquote>
<script async="" src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
<p>All the way back in April 2024, independent developers had <em>already</em> implemented pinned posts in a handful of client apps. They did so by using a <code>pinnedPost</code> field on the <code>app.bsky.actor.profile</code> record, as a simple string URL. This worked fine for several months, and multiple separate client apps (<a href="https://klearsky.pages/" target="_blank" rel="noopener noreferrer">Klearsky</a>, <a href="https://tokimeki.blue/" target="_blank" rel="noopener noreferrer">Tokimeki</a>, and <a href="https://hagoromo.relog.tech/" target="_blank" rel="noopener noreferrer">Hagoromo</a>) collaborated informally and used this same extension of the profile record type.</p>
<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:ilxxgyz7oz7mysber4omeqrg/app.bsky.feed.post/3krbslukrq22k" data-bluesky-cid="bafyreig3vaklphdozmgby4b4v7ggvxfs764diu4mtveifzxlamj2gcvchi"><p lang="ja">やっていることは簡単で、 app.bsky.actor.profile に pinnedPost というカスタムフィールドを作り、これにポストのAT URIを設定しているだけ…なんですが getProfile がカスタムフィールドを返してくれない（それはそう）のがちょっとあれでまだ調整中です</p>— mimonelu 🦀 みもねる (<a href="https://bsky.app/profile/did:plc:ilxxgyz7oz7mysber4omeqrg?ref_src=embed">@mimonelu.net</a>) <a href="https://bsky.app/profile/did:plc:ilxxgyz7oz7mysber4omeqrg/post/3krbslukrq22k?ref_src=embed">Apr 29, 2024 at 8:45 AM</a></blockquote>
<script async="" src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
<p>One of the interesting dynamics was that multiple independent Bluesky apps were collaborating to use the same extension field.</p>
<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:qcruz6j42vap6uhur4yeisef/app.bsky.feed.post/3krlxdxdfdd2w" data-bluesky-cid="bafyreiaple5g5a6s6e37ufsn3ykfrn776ijmfrwnmpy7u4nf6gf37tryha"><p lang="ja">Blueskyクライアントの一覧を更新しました！🆕Features!PinnedPost (3rd party non-official feature)・Klearsky・TOKIMEKI・羽衣-Hagoromo-<br><br><a href="https://bsky.app/profile/did:plc:qcruz6j42vap6uhur4yeisef/post/3krlxdxdfdd2w?ref_src=embed">[image or embed]</a></p>— どるちぇ (<a href="https://bsky.app/profile/did:plc:qcruz6j42vap6uhur4yeisef?ref_src=embed">@l-tan.blue</a>) <a href="https://bsky.app/profile/did:plc:qcruz6j42vap6uhur4yeisef/post/3krlxdxdfdd2w?ref_src=embed">May 3, 2024 at 9:36 AM</a></blockquote>
<script async="" src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
<p>Which all worked great! Until the Bluesky update conflicted with the existing records, causing errors for some users. Under the new schema, the previously-written records suddenly became "invalid". And new records, valid under the new schema, could be invalid from the perspective of independent software.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="analysis">Analysis<a href="https://docs.bsky.app/blog/pinned-posts#analysis" class="hash-link" aria-label="Direct link to Analysis" title="Direct link to Analysis">​</a></h3>
<p>The issue with conflicting records was an unintentional mistake on our part. While we knew that other apps had experimented with pinned posts, and separately knew that conflicts with Lexicon extension fields were possible in theory, we didn't check or ask around for feedback when updating the profile schema. While the Bluesky app is open-source and this new schema had even been discussed by developers in the app ahead of time, we didn't realize we had a name collision until the app update was shipped out to millions of users. If we had known about the name collision in advance, we would have chosen a different field name or worked with the dev community to resolve the issue.</p>
<p>There has not been clear guidance to developers about how to interoperate with and extend Lexicons defined by others. While we have discussed these questions publicly a few times, the specifications are somewhat buried, and we are just starting to document guidance and best practices.</p>
<p>At the heart of this situation is a tension over who controls and maintains Lexicions. The design of the system is that authority is rooted in the domain name corresponding to the schema NSID (in reverse notation). In this example, the app.bsky.actor.profile schema is controlled by the owners of bsky.app – the Bluesky team. Ideally schema maintainers will collaborate with other developers to update the authoritative schemas with additional fields as needed.</p>
<p>There is some flexibility in the validation rules to allow forwards-compatible evolution of schemas. Off-schema attributes can be inserted, ignored during schema validation, and passed through to downstream clients. Consequently it’s possible (and acceptable) for other clients to use off-schema attributes, which is the situation that happened here.</p>
<p>While this specific case resulted in interoperability problems, we want to point out that these same apps are separately demonstrating a strong form of interoperation by including data from multiple schemas (whtwnd.com, linkat.blue, etc) all in a single app. This is exactly the kind of robust data reuse and collaboration we hoped the Lexicon system would enable.</p>
<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:4tr5dqti7nmu6g2czpthntak/app.bsky.feed.post/3l67oc7d2pq27" data-bluesky-cid="bafyreieopijbllr7ydxjxa5gsba2cyba3wrmnzhmdourpzcqfqsu2kef2y"><p lang="ja">🌈 TOKIMEKI UPDATE!!!(Web/Android v1.3.5/iOS TF)🆕 プロフィール画面に Atmosphere スペースを追加！- AT Protocol では Bluesky 以外にも様々なサービスを自由に開発することができ、実際にいくつかの便利なサービスが公開されています。- ユーザーが利用しているBluesky以外のサービスへのリンクを見ることができます。- 現在は、Linkat (リンク集) と WhiteWind (ブログ) の2つに対応。- 設定→全般から非表示にできます。Web | Android<br><br><a href="https://bsky.app/profile/did:plc:4tr5dqti7nmu6g2czpthntak/post/3l67oc7d2pq27?ref_src=embed">[image or embed]</a></p>— 🌈 TOKIMEKI Bluesky (<a href="https://bsky.app/profile/did:plc:4tr5dqti7nmu6g2czpthntak?ref_src=embed">@tokimeki.blue</a>) <a href="https://bsky.app/profile/did:plc:4tr5dqti7nmu6g2czpthntak/post/3l67oc7d2pq27?ref_src=embed">Oct 10, 2024 at 10:50 PM</a></blockquote>
<script async="" src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="current-recommendations">Current Recommendations<a href="https://docs.bsky.app/blog/pinned-posts#current-recommendations" class="hash-link" aria-label="Direct link to Current Recommendations" title="Direct link to Current Recommendations">​</a></h3>
<p>What do we recommend to developers looking to extend record schemas today?</p>
<p>Our current recommendation is to define new Lexicons for "sidecar" records. Instead of adding fields to <code>app.bsky.actor.profile</code>, define a new record schema (eg com.yourapp.profile) and put the fields there. When rendering a profile view, fetch this additional record at the same time. Some records always have a fixed record key, like <code>self</code>, so they can be fetched with a simple GET. For records like <code>app.bsky.feed.post</code>, which have TID record keys, the sidecar records can have the same record key as the original post, so they also can be fetched with a simple GET. We use this pattern at scale in the bsky Lexicons with <code>app.bsky.feed.threadgate</code>, which extends the post schema, and allows data updates without changing the version (CID) of the post record itself.</p>
<p>There is some overhead to doing additional fetches, but these can be mitigated with caching or building a shim API server (with updated API Lexicions) to blend in the additional data to "view" requests. If needed, support could be improved with generic APIs to automatically hydrate "related records" with matching TIDs across collections in the same repository.</p>
<p>If sidecar records are not an option, and developers feel they must add data directly to existing record types, we very strongly recommend against field names that might conflict. Even if you think other developers might want to use the same extension, you should intentionally choose long unique prefixes for field names to prevent conflicts both with the "authoritative" Lexicon author, and other developers who might try to make the same extension. What we currently recommend is using a long, unique, non-generic project name prefix, or even a full NSID for the field name. For example, <code>app.graysky.pinnedPost</code> or <code>grayskyPinnedPost</code> are acceptable, but not <code>pinnedPost</code> or <code>extPinnedPost</code>.</p>
<p>While there has been some clever and admirable use of extension fields (the <a href="https://astrolabe-famz.onrender.com/at/redsolver.dev/app.bsky.feed.generator/aaaf7pgw4xqhu" target="_blank" rel="noopener noreferrer">SkyFeed configuration mechanism</a> in <code>app.bsky.feed.generator</code> records comes to mind), we don't see inserting fields into data specified by other parties as a reliable or responsible practice in the long run. We acknowledge that there is a demonstrated demand for a simple extension mechanism, and safer ways to insert extension data in records might be specified in the future.</p>
<p>Proposals and discussion welcome! There is an <a href="https://github.com/bluesky-social/atproto/discussions/1889" target="_blank" rel="noopener noreferrer">existing thread on Github</a>.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="progress-with-lexicons">Progress with Lexicons<a href="https://docs.bsky.app/blog/pinned-posts#progress-with-lexicons" class="hash-link" aria-label="Direct link to Progress with Lexicons" title="Direct link to Progress with Lexicons">​</a></h3>
<p>While not directly related to extension fields, we have a bunch of ongoing work with the overall system.</p>
<p>We are designing a mechanism for Lexicon resolution. This will allow anybody on the public internet to authoritatively resolve the schema for a given NSID. This process should not need to happen very often, and we want to incorporate lessons from previous live schema validation systems (including XML), but there does need to be a way to demonstrate authority.</p>
<p>We are planning to build an aggregator and automated documentation system for Lexicons, similar to package management systems like <a href="https://pkg.go.dev/" target="_blank" rel="noopener noreferrer">pkg.go.dev</a> and <a href="https://lib.rs/" target="_blank" rel="noopener noreferrer">lib.rs</a>. These will make it easier to discover and work with independent Lexicons across the ATmosphere and provide baseline documentation of schemas for developers. They can also provide collective benefits such as archiving, flagging abuse and security problems, and enabling research.</p>
<p>We are writing a style guide for authoring Lexicons, with design patterns, tips and common gotchas, and considerations for evolution and extensibility.</p>
<p>The validation behaviors for the <code>unknown</code> and <code>union</code> Lexicon types <a href="https://github.com/bluesky-social/atproto-website/pull/349" target="_blank" rel="noopener noreferrer">have been clarified</a> in the specifications.</p>
<p>The schema validation behavior when records are created at PDS instances has been updated, and will be reflected in the specifications soon (<a href="https://github.com/bluesky-social/atproto-website/issues/353" target="_blank" rel="noopener noreferrer">a summary is available</a>).</p>
<p>Generic run-time Lexicon validation support was <a href="https://github.com/bluesky-social/indigo/pull/420" target="_blank" rel="noopener noreferrer">added to the Go SDK</a> (indigo), and test vectors were <a href="https://github.com/bluesky-social/atproto-interop-tests/tree/main/lexicon" target="_blank" rel="noopener noreferrer">added to the atproto interop tests repository</a>.</p>
<p>Finally, an end-to-end <a href="https://atproto.com/guides/applications" target="_blank" rel="noopener noreferrer">tutorial on building an example app</a> ("Statusphere") using custom Lexicons was added to the updated atproto documentation website.</p>
<p>Overall, the process for designing and publishing new schemas from scratch should be clearer soon, and the experience of finding and working with existing schemas should be significantly improved as well.</p>]]></content>
        <category label="lexicon" term="lexicon"/>
        <category label="interop" term="interop"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[OAuth for AT Protocol]]></title>
        <id>https://docs.bsky.app/blog/oauth-atproto</id>
        <link href="https://docs.bsky.app/blog/oauth-atproto"/>
        <updated>2024-09-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We are very happy to release the initial specification of OAuth for AT Protocol! This is expected to be the primary authentication and authorization system between atproto client apps and PDS instances going forward, replacing the current flow using App Passwords and createSession over time.]]></summary>
        <content type="html"><![CDATA[<p>We are very happy to release the initial specification of OAuth for AT Protocol! This is expected to be the primary authentication and authorization system between atproto client apps and PDS instances going forward, replacing the current flow using App Passwords and <code>createSession</code> over time.</p>
<p>OAuth is a framework of standards under active development by the IETF. We selected a particular "profile" of RFCs, best practices, and draft specifications to preserve security in the somewhat unique atproto ecosystem. In particular, unlike most existing OAuth deployments and integrations, the atproto network is composed of many independent server instances, client apps, developers, and end users, who generally have not cross-registered their client software ahead of time. This necessitates both automated discovery of the user’s Authorization Server, and automated registration of client metadata with the server. In some ways this situation is closer to that between email clients and email providers than it is between traditionally pre-registered OAuth or OIDC clients (such as GitHub apps or "Sign In With Google"). This unfortunately means that generic OAuth client libraries may not work out-of-the-box with the atproto profile yet. We have built on top of draft standards (including <a href="https://datatracker.ietf.org/doc/draft-parecki-oauth-client-id-metadata-document/" target="_blank" rel="noopener noreferrer">"OAuth Client ID Metadata Document"</a>) and are optimistic that library support will improve with time.</p>
<p>We laid out an <a href="https://github.com/bluesky-social/atproto/discussions/2656" target="_blank" rel="noopener noreferrer">OAuth Roadmap</a> earlier this summer, and are entering the "Developer Preview Phase". In the coming months we expect to tweak the specification based on feedback from developers and standards groups, and to fill in a few details. We expect the broad shape of the specification to remain the same, and encourage application and SDK developers to start working with the specification now, and stop using the legacy App Password system for new projects.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-is-ready-today">What is Ready Today?<a href="https://docs.bsky.app/blog/oauth-atproto#what-is-ready-today" class="hash-link" aria-label="Direct link to What is Ready Today?" title="Direct link to What is Ready Today?">​</a></h3>
<p>OAuth has been deployed to several components of the atproto network over the past weeks. The Bluesky-developed PDS implementation implements the server component (including the Authorization Interface), the TypeScript client SDK now supports the client components, and several independent developers and projects have implemented login flows. At this time the Bluesky Social app has not yet been updated to use OAuth.</p>
<p>We have a a few resources for developers working with OAuth:</p>
<ul>
<li>The <strong>TypeScript SDK</strong> (<code>@atproto/api</code>) has been updated to support OAuth. READMEs are available for a <a href="https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser" target="_blank" rel="noopener noreferrer">browser-specific package</a> and a <a href="https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-node" target="_blank" rel="noopener noreferrer">Node.js-specific package</a>. There is also a browser-based <a href="https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser-example" target="_blank" rel="noopener noreferrer">example project</a>. You can read more about upgrading in the <a href="https://docs.bsky.app/blog/ts-api-refactor" target="_blank" rel="noopener noreferrer">Typescript API Package Auth Refactor blog post</a>.</li>
<li><a href="https://docs.bsky.app/docs/advanced-guides/oauth-client" target="_blank" rel="noopener noreferrer"><strong>OAuth Client Implementation Guide</strong></a>, a developer-oriented document.</li>
<li>A <a href="https://github.com/bluesky-social/cookbook/blob/main/python-oauth-web-app/README.md" target="_blank" rel="noopener noreferrer"><strong>Python (Flask) Web Service Demo</strong></a> which shows how to implement a client "the hard way", without using a supporting SDK. A live version of the demo is running at <a href="https://oauth-flask.demo.bsky.dev/" target="_blank" rel="noopener noreferrer">https://oauth-flask.demo.bsky.dev/</a> (it may not be operated indefinitely).</li>
<li>The <a href="https://atproto.com/specs/auth" target="_blank" rel="noopener noreferrer"><strong>AT Protocol OAuth Specification</strong></a>, which is the authoritative reference.</li>
</ul>
<p>The current OAuth profile does not specify how granular permissions ("scopes") work with atproto. Instead, we have defined a small set of "transitional" scopes which provide the same levels of client access as the current auth system:</p>
<ul>
<li><code>transition:generic</code> the same level of permissions as an App Password</li>
<li><code>transition:chat.bsky</code> is an add-on (must be included in combination with <code>transition:generic</code>) which adds access to the <code>chat.bsky.*</code> Lexicons for DMs. Same behavior as an App Password with the DM access option selected.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="next-steps">Next Steps<a href="https://docs.bsky.app/blog/oauth-atproto#next-steps" class="hash-link" aria-label="Direct link to Next Steps" title="Direct link to Next Steps">​</a></h3>
<p>There are two larger components which will integrate OAuth into the atproto ecosystem:</p>
<ul>
<li>The first is to expand the PDS account web interface to manage active OAuth sessions. This will allow users to inspect active sessions, the associated clients, and to revoke those sessions (eg, "remote log out"). This is user interface work specific to the PDS implementation, and will not change or impact the existing OAuth specification.</li>
<li>The second is to design an atproto-native scopes system which integrates with Lexicons, record collections, and other protocol features. This will allow granular app permissions in an extensible manner. This is expected to be orthogonal to the current OAuth specification, and it should be relatively easy for client apps to transition to more granular permissions, though it will likely require logout and re-authentication by users.</li>
</ul>
<p>These are big priorities for user security, and the path to implementation and deployment is clear.</p>
<p>While that work is in progress, we are interested in feedback from SDK developers and early adopters. What pain points do you encounter? Are there requirements which could be relaxed without reducing user security?</p>
<p>The overall design of this OAuth profile is similar to that of other social web protocols, such as ActivityPub. There are some atproto-specific aspects, but we are open to collaboration and harmonization between profiles to simplify and improve security on the web generally.</p>
<p>Finally, a number of the specifications we adopt and build upon are still drafts undergoing active development. We are interested in feedback on our specification, and intend to work with standards bodies (including the IETF) and tweak our profile if necessary to ensure compliance with final versions of any relevant standards and best practices.</p>]]></content>
        <category label="oauth" term="oauth"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Typescript API Package Auth Refactor]]></title>
        <id>https://docs.bsky.app/blog/ts-api-refactor</id>
        <link href="https://docs.bsky.app/blog/ts-api-refactor"/>
        <updated>2024-08-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Today we are merging some changes to how the TypeScript @atproto/api package works with authentication sessions. The changes are mostly backwards compatible, but some parts are now deprecated, and there are some breaking changes for advanced uses.]]></summary>
        <content type="html"><![CDATA[<p>Today we are merging some changes to how the TypeScript <code>@atproto/api</code> package works with authentication sessions. The changes are mostly backwards compatible, but some parts are now deprecated, and there are some breaking changes for advanced uses.</p>
<p>The motivation for these changes is the need to make the <code>@atproto/api</code> package compatible with OAuth session management. We don't have OAuth client support "launched" and documented quite yet, so you can keep using the current app password authentication system. When we do "launch" OAuth support and begin encouraging its usage in the near future (see the <a href="https://github.com/bluesky-social/atproto/discussions/2656" target="_blank" rel="noopener noreferrer">OAuth Roadmap</a>), these changes will make it easier to migrate.</p>
<p>In addition, the redesigned session management system fixes a bug that could cause the session data to become invalid when Agent clones are created (e.g. using <code>agent.withProxy()</code>).</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="new-features">New Features<a href="https://docs.bsky.app/blog/ts-api-refactor#new-features" class="hash-link" aria-label="Direct link to New Features" title="Direct link to New Features">​</a></h2>
<p>We've restructured the <code>XrpcClient</code> HTTP fetch handler to be specified during the instantiation of the XRPC client, through the constructor, instead of using a default implementation (which was statically defined).</p>
<p>With this refactor, the XRPC client is now more modular and reusable. Session management, retries, cryptographic signing, and other request-specific logic can be implemented in the fetch handler itself rather than by the calling code.</p>
<p>A new abstract class named <code>Agent</code>, has been added to <code>@atproto/api</code>. This class will be the base class for all Bluesky agents classes in the <code>@atproto</code> ecosystem. It is meant to be extended by implementations that provide session management and fetch handling. Here is the class hierarchy:</p>
<p><img decoding="async" loading="lazy" alt="AT protocol api class hierarchy" src="https://docs.bsky.app/assets/images/class-structure-0cb86fa36902c22c6e88fc7fb7def73c.png" width="2026" height="1855" class="img_ev3q"></p>
<p>As you adapt your code to these changes, make sure to use the <code>Agent</code> type wherever you expect to receive an agent, and use the <code>AtpAgent</code> type (class) only to instantiate your client. The reason for this is to be forward compatible with the OAuth agent implementation that will also extend <code>Agent</code>, and not <code>AtpAgent</code>.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Agent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> AtpAgent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setupAgent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">service</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> username</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> password</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">Agent</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> agent </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">AtpAgent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    service</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">persistSession</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">evt</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> session</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// handle session update</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> agent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">login</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">username</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> password</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> agent</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Agent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">doStuffWithAgent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">agent</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Agent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> arg</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> agent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">resolveHandle</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">arg</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Agent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> AtpAgent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyClass</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  agent</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Agent</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">agent </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">AtpAgent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="breaking-changes">Breaking changes<a href="https://docs.bsky.app/blog/ts-api-refactor#breaking-changes" class="hash-link" aria-label="Direct link to Breaking changes" title="Direct link to Breaking changes">​</a></h2>
<p>Most of the changes introduced in this version are backward-compatible. However, there are a couple of breaking changes you should be aware of:</p>
<ul>
<li>Customizing <code>fetch</code>: The ability to customize the <code>fetch: FetchHandler</code> property of <code>@atproto/xrpc</code>'s <code>Client</code> and <code>@atproto/api</code>'s <code>AtpAgent</code> classes has been removed. Previously, the <code>fetch</code> property could be set to a function that would be used as the fetch handler for that instance, and was initialized to a default fetch handler. That property is still accessible in a read-only fashion through the <code>fetchHandler</code> property and can only be set during the instance creation. Attempting to set/get the <code>fetch</code> property will now result in an error.</li>
<li>The <code>fetch()</code> method, as well as WhatWG compliant <code>Request</code> and <code>Headers</code> constructors, must be globally available in your environment. Use a polyfill if necessary.</li>
<li>The <code>AtpBaseClient</code> has been removed. The <code>AtpServiceClient</code> has been renamed <code>AtpBaseClient</code>. Any code using either of these classes will need to be updated.</li>
<li>Instead of <em>wrapping</em> an <code>XrpcClient</code> in its <code>xrpc</code> property, the <code>AtpBaseClient</code> (formerly <code>AtpServiceClient</code>) class - created through <code>lex-cli</code> - now <em>extends</em> the <code>XrpcClient</code> class. This means that a client instance now passes the <code>instanceof XrpcClient</code> check. The <code>xrpc</code> property now returns the instance itself and has been deprecated.</li>
<li><code>setSessionPersistHandler</code> is no longer available on the <code>AtpAgent</code> or <code>BskyAgent</code> classes. The session handler can only be set though the <code>persistSession</code> options of the <code>AtpAgent</code> constructor.</li>
<li>The new class hierarchy is as follows:<!-- -->
<ul>
<li><code>BskyAgent</code> extends <code>AtpAgent</code>: but add no functionality (hence its deprecation).</li>
<li><code>AtpAgent</code> extends <code>Agent</code>: adds password based session management.</li>
<li><code>Agent</code> extends <code>AtpBaseClient</code>: this abstract class that adds syntactic sugar methods <code>app.bsky</code> lexicons. It also adds abstract session management methods and adds atproto specific utilities (<code>labelers</code> &amp; <code>proxy</code> headers, cloning capability)  - <code>AtpBaseClient</code> extends <code>XrpcClient</code>: automatically code that adds fully typed lexicon defined namespaces (<code>instance.app.bsky.feed.getPosts()</code>) to the <code>XrpcClient</code>.</li>
<li><code>XrpcClient</code> is the base class.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="non-breaking-changes">Non-breaking changes<a href="https://docs.bsky.app/blog/ts-api-refactor#non-breaking-changes" class="hash-link" aria-label="Direct link to Non-breaking changes" title="Direct link to Non-breaking changes">​</a></h2>
<ul>
<li>The <code>com.*</code> and <code>app.*</code> namespaces have been made directly available to every <code>Agent</code> instances.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="deprecations">Deprecations<a href="https://docs.bsky.app/blog/ts-api-refactor#deprecations" class="hash-link" aria-label="Direct link to Deprecations" title="Direct link to Deprecations">​</a></h2>
<ul>
<li>The default export of the <code>@atproto/xrpc</code> package has been deprecated. Use named exports instead.</li>
<li>The <code>Client</code> and <code>ServiceClient</code> classes are now deprecated. They are replaced by a single <code>XrpcClient</code> class.</li>
<li>The default export of the <code>@atproto/api</code> package has been deprecated. Use named exports instead.</li>
<li>The <code>BskyAgent</code> has been deprecated. Use the <code>AtpAgent</code> class instead.</li>
<li>The <code>xrpc</code> property of the <code>AtpClient</code> instances has been deprecated. The instance itself should be used as the XRPC client.</li>
<li>The <code>api</code> property of the <code>AtpAgent</code> and <code>BskyAgent</code> instances has been deprecated. Use the instance itself instead.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="migration">Migration<a href="https://docs.bsky.app/blog/ts-api-refactor#migration" class="hash-link" aria-label="Direct link to Migration" title="Direct link to Migration">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-atprotoapi-package">The <code>@atproto/api</code> package<a href="https://docs.bsky.app/blog/ts-api-refactor#the-atprotoapi-package" class="hash-link" aria-label="Direct link to the-atprotoapi-package" title="Direct link to the-atprotoapi-package">​</a></h3>
<p>If you were relying on the <code>AtpBaseClient</code> solely to perform validation, use this:</p>
<table><tbody><tr><td><center>Before</center></td><td><center>After</center></td></tr><tr><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AtpBaseClient</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ComAtprotoSyncSubscribeRepos </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> baseClient </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">AtpBaseClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">baseClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">xrpc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">lex</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">assertValidXrpcMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> lexicons </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">lexicons</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">assertValidXrpcMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td></tr></tbody></table>
<p>If you are extending the <code>BskyAgent</code> to perform custom <code>session</code> manipulation, define your own <code>Agent</code> subclass instead:</p>
<table><tbody><tr><td><center>Before</center></td><td><center>After</center></td></tr><tr><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> BskyAgent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyAgent</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">BskyAgent</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> accessToken</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createOrRefreshSession</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">identifier</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> password</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// custom logic here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-access-jwt'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">doStuff</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">call</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'Authorization'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">accessToken </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation keyword" style="color:#00009f">this</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">accessToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Agent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyAgent</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">Agent</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> accessToken</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> did</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">readonly</span><span class="token plain"> service</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      service</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function-variable function" style="color:#d73a49">Authorization</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">accessToken </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation keyword" style="color:#00009f">this</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">accessToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">clone</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> MyAgent </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> agent </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">MyAgent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">service</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    agent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">accessToken</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    agent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">did </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">did</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">copyInto</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">agent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createOrRefreshSession</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">identifier</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> password</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// custom logic here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">did </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'did:example:123'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-access-jwt'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td></tr></tbody></table>
<p>If you are monkey patching the <code>xrpc</code> service client to perform client-side rate limiting, you can now do this in the <code>FetchHandler</code> function:</p>
<table><tbody><tr><td><center>Before</center></td><td><center>After</center></td></tr><tr><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> BskyAgent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> RateLimitThreshold </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rate-limit-threshold"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> agent </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">BskyAgent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> limiter </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">RateLimitThreshold</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token number" style="color:#36acaa">300_000</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> origCall </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> agent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">api</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">xrpc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">call</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">agent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">api</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">xrpc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function-variable function" style="color:#d73a49">call</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">...</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> limiter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">wait</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">origCall</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">call</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AtpAgent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> RateLimitThreshold </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rate-limit-threshold"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">LimitedAtpAgent</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">AtpAgent</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AtpAgentOptions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> fetch </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> options</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fetch </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> globalThis</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fetch</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> limiter </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">RateLimitThreshold</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token number" style="color:#36acaa">300_000</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">options</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">fetch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">...</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> limiter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">wait</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">...</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td></tr></tbody></table>
<p>If you configure a static <code>fetch</code> handler on the <code>BskyAgent</code> class - for example to modify the headers of every request - you can now do this by providing your own <code>fetch</code> function:</p>
<table><tbody><tr><td><center>Before</center></td><td><center>After</center></td></tr><tr><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> BskyAgent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> defaultFetchHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">BskyAgent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">configure</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">fetch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">httpUri</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpMethod</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpHeaders</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpReqBody</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> ua </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> httpHeaders</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"User-Agent"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    httpHeaders</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"User-Agent"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ua </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">ua</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"> </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">userAgent</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userAgent</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defaultFetchHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">httpUri</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpMethod</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpHeaders</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpReqBody</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AtpAgent </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyAtpAgent</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">AtpAgent</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AtpAgentOptions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> fetch </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> options</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fetch </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> globalThis</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fetch</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">options</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">fetch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> init</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> headers </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Headers</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">init</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> ua </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> headersList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"User-Agent"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        headersList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"User-Agent"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ua </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">ua</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"> </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">userAgent</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> userAgent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">init</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> headers </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td></tr></tbody></table>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-atprotoxrpc-package">The <code>@atproto/xrpc</code> package<a href="https://docs.bsky.app/blog/ts-api-refactor#the-atprotoxrpc-package" class="hash-link" aria-label="Direct link to the-atprotoxrpc-package" title="Direct link to the-atprotoxrpc-package">​</a></h3>
<p>The <code>Client</code> and <code>ServiceClient</code> classes are now <strong>deprecated</strong>. If you need a lexicon based client, you should update the code to use the <code>XrpcClient</code> class instead.</p>
<p>The deprecated <code>ServiceClient</code> class now extends the new <code>XrpcClient</code> class. Because of this, the <code>fetch</code> <code>FetchHandler</code> can no longer be configured on the <code>Client</code> instances (including the default export of the package). If you are not relying on the <code>fetch</code> <code>FetchHandler</code>, the new changes should have no impact on your code. Beware that the deprecated classes will eventually be removed in a
future version.</p>
<p>Since its use has completely changed, the <code>FetchHandler</code> type has also completely changed. The new <code>FetchHandler</code> type is now a function that receives a <code>url</code> pathname and a <code>RequestInit</code> object and returns a <code>Promise&lt;Response&gt;</code>. This function is responsible for making the actual request to the server.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">FetchHandler</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">this</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * The URL (pathname + query parameters) to make the request to, without the</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * origin. The origin (protocol, hostname, and port) must be added by this</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   * </span><span class="token doc-comment comment punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@link</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> FetchHandler</span><span class="token doc-comment comment punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic">, typically based on authentication or other factors.</span><br></span><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">   */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  init</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> RequestInit</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">Response</span><span class="token operator" style="color:#393A34">&gt;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>A noticeable change that has been introduced is that the <code>uri</code> field of the <code>ServiceClient</code> class has <em>not</em> been ported to the new <code>XrpcClient</code> class. It is now the responsibility of the <code>FetchHandler</code> to determine the full URL to make the request to. The same goes for the <code>headers</code>, which should now be set through the <code>FetchHandler</code> function.</p>
<p>If you <em>do</em> rely on the legacy <code>Client.fetch</code> property to perform custom logic upon request, you will need to migrate your code to use the new <code>XrpcClient</code> class. The <code>XrpcClient</code> class has a similar API to the old <code>ServiceClient</code> class, but with a few differences:</p>
<ul>
<li>The <code>Client</code> + <code>ServiceClient</code> duality was removed in favor of a single <code>XrpcClient</code> class. This means that:<!-- -->
<ul>
<li>There no longer exists a centralized lexicon registry. If you need a global lexicon registry, you can maintain one yourself using a <code>new Lexicons</code> (from <code>@atproto/lexicon</code>).</li>
<li>The <code>FetchHandler</code> is no longer a statically defined property of the <code>Client</code> class. Instead, it is passed as an argument to the <code>XrpcClient</code> constructor.</li>
</ul>
</li>
<li>The <code>XrpcClient</code> constructor now requires a <code>FetchHandler</code> function as the first argument, and an optional <code>Lexicon</code> instance as the second argument.</li>
<li>The <code>setHeader</code> and <code>unsetHeader</code> methods were not ported to the new <code>XrpcClient</code> class. If you need to set or unset headers, you should do so in the <code>FetchHandler</code> function provided in the constructor arg.</li>
</ul>
<table><tbody><tr><td><center>Before</center></td><td><center>After</center></td></tr><tr><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> defaultFetchHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/xrpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function-variable function" style="color:#d73a49">fetch</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  httpUri</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  httpMethod</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  httpHeaders</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Headers</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  httpReqBody</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Custom logic here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defaultFetchHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">httpUri</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpMethod</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpHeaders</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> httpReqBody</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addLexicon</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  lexicon</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  defs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> instance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">service</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'http://my-service.com'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">instance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'my-header'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-value'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">instance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">call</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td><td><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> XrpcClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/xrpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> instance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">XrpcClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> init</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> headers </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Headers</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">init</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'my-header'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-value'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Custom logic here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> fullUrl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'http://my-service.com'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fullUrl</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">init</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> headers </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      lexicon</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      defs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">instance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">call</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></td></tr></tbody></table>
<p>If your fetch handler does not require any "custom logic", and all you need is an <code>XrpcClient</code> that makes its HTTP requests towards a static service URL, the previous example can be simplified to:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> XrpcClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/xrpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> instance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">XrpcClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'http://my-service.com'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    lexicon</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    defs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>If you need to add static headers to all requests, you can instead instantiate the <code>XrpcClient</code> as follows:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> XrpcClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/xrpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> instance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">XrpcClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    service</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'http://my-service.com'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">'my-header'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-value'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      lexicon</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      defs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>If you need the headers or service url to be dynamic, you can define them using functions:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> XrpcClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@atproto/xrpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> instance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">XrpcClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">service</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'http://my-service.com'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">'my-header'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-value'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">'my-ignored-header'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// ignored</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      lexicon</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'io.example.doStuff'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      defs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Labeling Services Microgrants]]></title>
        <id>https://docs.bsky.app/blog/label-grants</id>
        <link href="https://docs.bsky.app/blog/label-grants"/>
        <updated>2024-05-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We’re launching microgrants for labeling services on Bluesky!]]></summary>
        <content type="html"><![CDATA[<p><strong>We’re launching microgrants for labeling services on Bluesky!</strong></p>
<p>Moderation is the backbone of healthy social spaces online. Bluesky has our own moderation team dedicated to providing around-the-clock coverage to uphold our <a href="https://bsky.social/about/support/community-guidelines" target="_blank" rel="noopener noreferrer">community guidelines</a>, and additionally, we recognize that there is no one-size-fits-all approach to moderation. No single company can get online safety right for every country, culture, and community in the world. So we’ve also been building something bigger — an ecosystem of moderation and open-source safety tools that gives communities power to create their own spaces, with their own norms and preferences.</p>
<p>Labeling services on Bluesky allow users and communities to participate in a stackable ecosystem of services. Users can create and subscribe to filters from independent moderation services, which are layered on top of Bluesky’s own service. You can read more about how stackable moderation works on Bluesky <a href="https://bsky.social/about/blog/03-12-2024-stackable-moderation" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p><strong>Update:</strong> As of 2025 we are not currently accepting further grant applications.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="to-support-the-first-labelers-in-our-ecosystem-and-encourage-more-we-are-launching-a-microgrants-program-for-labeling-services">To support the first labelers in our ecosystem and encourage more, we are launching a microgrants program for labeling services.<a href="https://docs.bsky.app/blog/label-grants#to-support-the-first-labelers-in-our-ecosystem-and-encourage-more-we-are-launching-a-microgrants-program-for-labeling-services" class="hash-link" aria-label="Direct link to To support the first labelers in our ecosystem and encourage more, we are launching a microgrants program for labeling services." title="Direct link to To support the first labelers in our ecosystem and encourage more, we are launching a microgrants program for labeling services.">​</a></h4>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="program-details">Program Details<a href="https://docs.bsky.app/blog/label-grants#program-details" class="hash-link" aria-label="Direct link to Program Details" title="Direct link to Program Details">​</a></h2>
<p>For this program, we have an allocation of $10,000. We will be distributing $500 per labeling service that is approved for a grant.</p>
<p>The application has a rolling deadline, and we will announce both the recipients of the grants and when all of the grants have been distributed. We pay out grants via public GitHub Sponsorships.</p>
<p>In addition, we’ve also partnered with Amazon Web Services (AWS) to offer $5,000 in <a href="https://aws.amazon.com/startups?lang=en-US#start" target="_blank" rel="noopener noreferrer">AWS Activate</a><sup><a href="https://docs.bsky.app/blog/label-grants#user-content-fn-1-ed7395" id="user-content-fnref-1-ed7395" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup> credits to labeling services as well. These credits are applied to your AWS bill to help cover costs from cloud services, including machine learning, compute, databases, storage, containers, dev tools, and more. Simply check a box in your grant application if you’re interested in receiving these credits as well.</p>
<p>If you’re an organization interested in running a labeler but do not currently have the technical capacity to implement one, please reach out to our team at <a href="mailto:partnerships@blueskyweb.xyz" target="_blank" rel="noopener noreferrer">partnerships@blueskyweb.xyz</a>. We may be able to assist in matching you with a developer.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="initial-labeling-grant-recipients">Initial Labeling Grant Recipients<a href="https://docs.bsky.app/blog/label-grants#initial-labeling-grant-recipients" class="hash-link" aria-label="Direct link to Initial Labeling Grant Recipients" title="Direct link to Initial Labeling Grant Recipients">​</a></h2>
<p>We're kicking off the program with grants to three initial recipients:</p>
<p><strong>XBlock</strong></p>
<p><a href="https://bsky.app/profile/xblock.aendra.dev" target="_blank" rel="noopener noreferrer">@XBlock.aendra.dev</a> is an attempt to help give users control over the types of content they see on Bluesky. Screenshots serve a variety of uses on social media, but quite often are intended to create discourse or drive dogpiles. By letting users toggle the visibility of screenshots from various platforms, XBlock aims to give users a "volume dial" for certain types of content.</p>
<p><strong>Aegis</strong></p>
<p><a href="https://bsky.app/profile/aegis.blue" target="_blank" rel="noopener noreferrer">@aegis.blue</a> is a volunteer-run labeling service providing community moderation predominantly to Bluesky's LGBTQIA+ and marginalized users. Featuring a diverse team of both industry and aspiring experts, Aegis lives by the motto, We Keep Us Safe. More info can be found on their website at <a href="https://aegis.blue/Home" target="_blank" rel="noopener noreferrer">https://aegis.blue/Home</a>.</p>
<p><strong>News Detective</strong></p>
<p>News Detective fights misinformation by combining the experience of professional factcheckers with the wisdom of crowds. A crowd of volunteer factcheckers transparently investigates posts, and professional factcheckers make sure only the highest quality factchecks make it through the system. Users who use News Detective will be able to see factchecks (including explanations and sources) on posts they come across and request factchecks on posts they find questionable. They can also watch News Detectives discuss the posts and even participate in factchecking to create a more honest, democratic, and transparent internet. Incubated at MIT DesignX, MIT Sandbox, and HacksHackers.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="contact">Contact<a href="https://docs.bsky.app/blog/label-grants#contact" class="hash-link" aria-label="Direct link to Contact" title="Direct link to Contact">​</a></h2>
<p>Please feel free to leave questions or comments on the GitHub discussion for this announcement <a href="https://github.com/bluesky-social/atproto/discussions/2496" target="_blank" rel="noopener noreferrer">here</a>, or on the Bluesky post <a href="https://bsky.app/profile/atproto.com/post/3ksmpstsett27" target="_blank" rel="noopener noreferrer">here</a>.</p>
<!-- -->
<section data-footnotes="true" class="footnotes"><h2 class="anchor anchorWithStickyNavbar_LWe7 sr-only" id="footnote-label">Footnotes<a href="https://docs.bsky.app/blog/label-grants#footnote-label" class="hash-link" aria-label="Direct link to Footnotes" title="Direct link to Footnotes">​</a></h2>
<ol>
<li id="user-content-fn-1-ed7395">
<p>AWS Activate Credits are subject to the program's <a href="https://aws.amazon.com/activate/terms/" target="_blank" rel="noopener noreferrer">terms and conditions</a>. <a href="https://docs.bsky.app/blog/label-grants#user-content-fnref-1-ed7395" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
        <category label="community" term="community"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 Protocol Roadmap]]></title>
        <id>https://docs.bsky.app/blog/2024-protocol-roadmap</id>
        <link href="https://docs.bsky.app/blog/2024-protocol-roadmap"/>
        <updated>2024-05-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Discuss this post in our Github Discussion forums here]]></summary>
        <content type="html"><![CDATA[<p><em>Discuss this post in our Github Discussion forums <a href="https://github.com/bluesky-social/atproto/discussions/2469" target="_blank" rel="noopener noreferrer">here</a></em></p>
<p>This roadmap is an update on our progress and lays out our general goals and focus for the coming months. This document is written for developers working on atproto clients, implementations, and applications (including Bluesky-specific projects). This is not a product announcement: while some product features are hinted at, we aren't promising specific timelines here. As always, most Bluesky software is free and open source, and observant folks can follow along with our progress week by week in GitHub.</p>
<p>In the big picture, we made a lot of progress on the protocol in early 2024. We opened up federation on the production network, demonstrated account migration, specified and launched stackable moderation (labeling and Ozone), shared our plan for OAuth, specified a generic proxying mechanism, built a new API documentation website (docs.bsky.app), and more.</p>
<p>After this big push on the protocol, the Bluesky engineering team is spending a few months catching up on some long-requested features like GIFs, video, and DMs. At the same time, we do have a few "enabling" pieces of protocol work underway, and continue to make progress towards a milestone of protocol maturity and stability.</p>
<p>Summary-level notes:</p>
<ul>
<li>Federation is now open: you don't need to pre-register in Discord any more.</li>
<li>It is increasingly possible to build independent apps and integrations on atproto. One early example is <a href="https://whtwnd.com/" target="_blank" rel="noopener noreferrer">https://whtwnd.com/</a>, a blogging web app built on atproto.</li>
<li>The timeline for a formal standards body process is being pushed back until we have additional independent active projects building on the protocol.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="current-work">Current Work<a href="https://docs.bsky.app/blog/2024-protocol-roadmap#current-work" class="hash-link" aria-label="Direct link to Current Work" title="Direct link to Current Work">​</a></h2>
<p><strong>Proxying of Independent Lexicons:</strong> earlier this year we added a <a href="https://atproto.com/specs/xrpc#service-proxying" target="_blank" rel="noopener noreferrer">generic HTTP proxying mechanism</a>, which allows clients to specify which onward service (eg, AppView) instance they want to communicate with. To date this has been limited to known Lexicons, but we will soon relax this restriction and make arbitrary XRPC query and procedure requests. Combined with allowing records with independent Lexicon schemas (now allowed), this finally enables building new independent atproto applications. <a href="https://github.com/bluesky-social/atproto/pull/2425" target="_blank" rel="noopener noreferrer">PR for this work</a></p>
<p><strong>Open Federation:</strong> the Bluesky Relay service initially required pre-registration before new PDS instances were crawled. This was a very informal process (using Discord) to prevent automated abuse, but we have removed this requirement, making it even easier to set up PDS instances. We will also bump the per-PDS account limits, though we will still enforce some limits to minimize automated abuse; these limits can be bumped for rapidly growing communities and projects.</p>
<p><strong>Email 2FA:</strong> while OAuth is our main focus for improving account security (OAuth flows will enable arbitrary MFA, including passkeys, hardware tokens, authenticators, etc), we are rapidly rolling out a basic form of 2FA, using an emailed code in addition to account password for sign-in. This will be an optional opt-in functionality. <a href="https://github.com/bluesky-social/atproto/discussions/2435" target="_blank" rel="noopener noreferrer">Announcement with details</a></p>
<p><strong>OAuth:</strong> we continue to make progress implementing our plan for OAuth. Ultimately this will completely replace the current account sign-up, session, and app-password API endpoints, though we will maintain backwards compatibility for a long period. With OAuth, account lifecycle, sign-in, and permission flows will be implementation-specific web views. This means that PDS implementations can add any sign-up screening or MFA methods they see fit, without needing support in the <code>com.atproto.*</code> Lexicons. <a href="https://github.com/bluesky-social/proposals/tree/main/0004-oauth" target="_blank" rel="noopener noreferrer">Detailed Proposal</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="product-features">Product Features<a href="https://docs.bsky.app/blog/2024-protocol-roadmap#product-features" class="hash-link" aria-label="Direct link to Product Features" title="Direct link to Product Features">​</a></h2>
<p>These are not directly protocol-related, but are likely to impact many developers, so we wanted to give a heads up on these.</p>
<p><strong>Harassment Mitigations:</strong> additional controls and mechanisms to reduce the prevalence, visibility, and impact of abusive mentions and replies, particularly coming from newly created single-purpose or throw-away accounts. May expand on the existing thread-gating and reply-gating functionality.</p>
<p><strong>Post Embeds:</strong> the ability to embed Bluesky posts in external public websites. Including oEmbed support. This has already shipped! See <a href="https://embed.bsky.app/" target="_blank" rel="noopener noreferrer">embed.bsky.app</a></p>
<p><strong>Basic "Off-Protocol" Direct Messages (DMs):</strong> having some mechanism to privately contact other Bluesky accounts is the most requested product feature. We looked closely at alternatives like linking to external services, re-using an existing protocol like Matrix, or rushing out on-protocol encrypted DMs, but ultimately decided to launch a basic centralized system to take the time pressure off our team and make our user community happy. We intend to iterate and fully support E2EE DMs as part of atproto itself, without a centralized service, and will take the time to get the user experience, security, and privacy polished. This will be a distinct part of the protocol from the repository abstraction, which is only used for public content.</p>
<p><strong>Better GIF and Video support:</strong> the first step is improving embeds from external platforms (like Tenor for GIFs, and YouTube for video). Both the post-creation flow and embed-view experience will be improved.</p>
<p><strong>Feed Interaction Metrics:</strong> feed services currently have no feedback on how users are interacting with the content that they curate. There is no way for users to tell specific feeds that they want to see more or less of certain kinds of content, or whether they have already seen content. We are adding a new endpoint for clients to submit behavior metrics to feed generators as a feedback mechanism. This feedback will be most useful for personalized feeds, and less useful for topic or community-oriented feeds. It also raises privacy and efficiency concerns, so sending of this metadata will both be controlled by clients (optional), and will require feed generator opt-in in the feed declaration record.</p>
<p><strong>Topic/Community Feeds:</strong> one of the more common uses for feed generators is to categorize content by topic or community. These feeds are not personalized (they look the same to all users), are not particularly "algorithmic" (posts are either in the feed or not), and often have relatively clear inclusion criteria (though they may be additionally curated or filtered). We are exploring ways to make it easier to create, curate, and explore this type of feed.</p>
<p><strong>User/Labeler Messaging:</strong> currently, independent moderators have no private mechanism to communicate with accounts which have reported content, or account which moderation actions have been taken against. All reports, including appeals, are uni-directional, and accounts have no record of the reports they have submitted. While Bluesky can send notification emails to accounts hosted on our own PDS instance, this does not work cross-provider with self-hosted PDS instances or independent labelers.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="protocol-stability-milestone">Protocol Stability Milestone<a href="https://docs.bsky.app/blog/2024-protocol-roadmap#protocol-stability-milestone" class="hash-link" aria-label="Direct link to Protocol Stability Milestone" title="Direct link to Protocol Stability Milestone">​</a></h2>
<p>A lot of progress has been made in recent months on the parts of the protocol relevant to large-scale public conversation. The core concepts of autonomous identity (DIDs and handles), self-certifying data (repositories), content curation (feed generators), and stackable moderation (labelers) have now all been demonstrated on the live network.</p>
<p>While we will continue to make progress on additional objectives (see below), we feel we are approaching a milestone in development and stability of these components of the protocol. There are a few smaller tasks to resolve towards this milestone.</p>
<p><strong>Takedowns:</strong> we have a written proposal for how content and account takedowns will work across different pieces of infrastructure in the network. Takedowns are a stronger intervention that complement the labeling system. Bluesky already has mechanisms to enact takedowns on our own infrastructure when needed, but there are some details of how inter-provider takedown requests are communicated.</p>
<p><strong>Remaining Written Specifications:</strong> a few parts of the protocol have not been written up in the specifications at atproto.com.</p>
<p><strong>Guidance on Building Apps and Integrations:</strong> while we hope the protocol will be adopted and built upon in unexpected ways, it would be helpful to have some basic pointers and advice on creating new applications and integrations. These will probably be informal tutorials and example code to start.</p>
<p><strong>Account and Identity Firehose Events:</strong> while account and identity state are authoritatively managed across the DID, DNS, and PDS systems, it is efficient and helpful for changes to this state to be broadcast over the repository event stream ("firehose"). The semantics and behavior of the existing <code>#identity</code> event type will be updated and clarified, and an additional <code>#account</code> event type will be added to communicate PDS account deletion and takedown state to downstream services (Relay, and on to AppView, feed generator, labelers, etc). Downstream services might still need to resolve state from an authoritative source after being notified on the firehose.</p>
<p><strong>Private Account Data Iteration:</strong> the <code>app.bsky</code> Lexicons currently include a preferences API, as well as some additional private state like mutes. The design of the current API is somewhat error-prone, difficult for independent developers to extend, and has unclear expectations around providing access to service providers (like independent AppViews). We are planning to iterate on this API, though it might not end up part of the near-term protocol milestone.</p>
<p><strong>Protocol Tech Debt:</strong> there are a few other small technical issues to resolve or clean up; these are tracked in <a href="https://github.com/bluesky-social/atproto/discussions/2128" target="_blank" rel="noopener noreferrer">this GitHub discussion</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="on-the-horizon">On the Horizon<a href="https://docs.bsky.app/blog/2024-protocol-roadmap#on-the-horizon" class="hash-link" aria-label="Direct link to On the Horizon" title="Direct link to On the Horizon">​</a></h2>
<p>There are a few other pieces of protocol work which we are starting to plan out, but which are not currently scheduled to complete in 2024. It is very possible that priorities and schedules will be shuffled, but we mostly want to call these out as things we do want to complete, but will take a bit more time.</p>
<p><strong>Protocol-Native DMs:</strong> as mentioned above, we want to have a "proper" DM solution as part of atproto, which is decentralized, E2EE, and follows modern security best practices.</p>
<p><strong>Limited-Audience (Non-Public) Content:</strong> to start, we have prioritized the large-scale public conversation use cases in our protocol design, centered around the public data repository concept. While we support using the right tool for the job, and atproto is not trying to encompass every possible social modality, there are many situations and use-cases where having limited-audience content in the same overall application would be helpful. We intend to build a mechanism for group-private content sharing. It will likely be distinct from public data repositories and the Relay/firehose mechanism, but retain other parts of the protocol stack.</p>
<p><strong>Firehose Bandwidth Efficiency:</strong> as the network grows, and the volume and rate of repository commits increases, the cost of subscribing to the entire Relay firehose increases. There are a number of ways to significantly improve bandwidth requirements: removing MST metadata for most use-cases; filtering by record types or subsets of accounts; batch compression; etc.</p>
<p><strong>Record Versioning (Post Editing):</strong> atproto already supports updating records in repositories: one example is updating bsky profile records. And preparations were made early in the protocol design to support post editing while avoiding misleading edits. Ideally, it would also be possible to (optionally) keep old versions of records around in the repository, and allow referencing and accessing multiple versions of the same record.</p>
<p><strong>PLC Transparency Log:</strong> we are exploring technical and organizational mechanisms to further de-centralize the DID PLC directory service. The most promising next step looks to be publishing a transparency log of all directory operations. This will make it easier for other organizations to audit the behavior of the directory and maintain verifiable replicas. The recent "tiling" transparency log design used for <a href="https://sunlight.dev/" target="_blank" rel="noopener noreferrer">https://sunlight.dev/</a> (<a href="https://filippo.io/a-different-CT-log" target="_blank" rel="noopener noreferrer">described here</a>) is particularly promising. Compatibility with <a href="https://www.rfc-editor.org/rfc/rfc6962" target="_blank" rel="noopener noreferrer">RFC 6962 (Certificate Transparency)</a> could allow future integration with an existing ecosystem of witnesses and auditors.</p>
<p><strong>Identity Key Self-Management UX:</strong> the DID PLC system has a concept of "rotation keys" to control the identity itself (in the form of the DID document). We would like to make it possible for users to optionally register additional keys on their personal devices, password managers, or hardware security keys. If done right, this should improve the resilience of the system and reduce some of the burden of responsibility on PDS operators. While this is technically possible today, it will require careful product design and security review to make this a safe and widely-adopted option.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="standards-body-timeline">Standards Body Timeline<a href="https://docs.bsky.app/blog/2024-protocol-roadmap#standards-body-timeline" class="hash-link" aria-label="Direct link to Standards Body Timeline" title="Direct link to Standards Body Timeline">​</a></h2>
<p>As described in our <a href="https://docs.bsky.app/blog/protocol-roadmap#protocol-governance" target="_blank" rel="noopener noreferrer">2023 Protocol Roadmap</a>, we hope to bring atproto to an existing standards body to solidify governance and interoperability of the lower levels of the protocol. We had planned to start the formal process this summer, but as we talked to more people experienced with this process, we realized that we should wait until the design of the protocol has been explored by more developers. It would be ideal to have a couple organizations with atproto experience collaborate on the standards process together. If you are interested in being part of the atproto standards process, leave a message in <a href="https://github.com/bluesky-social/atproto/discussions/2469" target="_blank" rel="noopener noreferrer">the discussion thread for this post</a>, or email <code>protocol@blueskyweb.xyz</code>.</p>
<p>While there has been a flowering of many projects built around the <code>app.bsky</code> microblogging application, there have been very few additional Lexicons and applications built from scratch. Some of this stemmed from restrictions on data schemas and proxying behavior on the Bluesky-hosted PDS instances, only relaxed just recently. We hope that new apps and Lexicons will exercise the full capabilities and corner-cases of the protocol.</p>
<p>We will continue to participate in adjacent standards efforts to make connections and get experience. Bluesky staff will attend <a href="https://www.ietf.org/meeting/120/" target="_blank" rel="noopener noreferrer">IETF 120 in July</a>, and are always happy to discuss responsible DNS integrations, OAuth, and HTTP API best practices.</p>]]></content>
        <category label="updates" term="updates"/>
    </entry>
</feed>