Typescript API Package Auth Refactor
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.
The motivation for these changes is the need to make the @atproto/api 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 OAuth Roadmap), these changes will make it easier to migrate.
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 agent.withProxy()).
New Features
We've restructured the XrpcClient 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).
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.
A new abstract class named Agent, has been added to @atproto/api. This class will be the base class for all Bluesky agents classes in the @atproto ecosystem. It is meant to be extended by implementations that provide session management and fetch handling. Here is the class hierarchy:

As you adapt your code to these changes, make sure to use the Agent type wherever you expect to receive an agent, and use the AtpAgent 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 Agent, and not AtpAgent.
import { Agent, AtpAgent } from '@atproto/api'
async function setupAgent(service: string, username: string, password: string): Promise<Agent> {
  const agent = new AtpAgent({
    service,
    persistSession: (evt, session) => {
      // handle session update
    },
  })
  await agent.login(username, password)
  return agent
}
import { Agent } from '@atproto/api'
async function doStuffWithAgent(agent: Agent, arg: string) {
  return agent.resolveHandle(arg)
}
import { Agent, AtpAgent } from '@atproto/api'
class MyClass {
  agent: Agent
  constructor () {
    this.agent = new AtpAgent()
  }
}
Breaking changes
Most of the changes introduced in this version are backward-compatible. However, there are a couple of breaking changes you should be aware of:
- Customizing 
fetch: The ability to customize thefetch: FetchHandlerproperty of@atproto/xrpc'sClientand@atproto/api'sAtpAgentclasses has been removed. Previously, thefetchproperty 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 thefetchHandlerproperty and can only be set during the instance creation. Attempting to set/get thefetchproperty will now result in an error. - The 
fetch()method, as well as WhatWG compliantRequestandHeadersconstructors, must be globally available in your environment. Use a polyfill if necessary. - The 
AtpBaseClienthas been removed. TheAtpServiceClienthas been renamedAtpBaseClient. Any code using either of these classes will need to be updated. - Instead of wrapping an 
XrpcClientin itsxrpcproperty, theAtpBaseClient(formerlyAtpServiceClient) class - created throughlex-cli- now extends theXrpcClientclass. This means that a client instance now passes theinstanceof XrpcClientcheck. Thexrpcproperty now returns the instance itself and has been deprecated. setSessionPersistHandleris no longer available on theAtpAgentorBskyAgentclasses. The session handler can only be set though thepersistSessionoptions of theAtpAgentconstructor.- The new class hierarchy is as follows:
BskyAgentextendsAtpAgent: but add no functionality (hence its deprecation).AtpAgentextendsAgent: adds password based session management.AgentextendsAtpBaseClient: this abstract class that adds syntactic sugar methodsapp.bskylexicons. It also adds abstract session management methods and adds atproto specific utilities (labelers&proxyheaders, cloning capability) -AtpBaseClientextendsXrpcClient: automatically code that adds fully typed lexicon defined namespaces (instance.app.bsky.feed.getPosts()) to theXrpcClient.XrpcClientis the base class.
 
Non-breaking changes
- The 
com.*andapp.*namespaces have been made directly available to everyAgentinstances. 
Deprecations
- The default export of the 
@atproto/xrpcpackage has been deprecated. Use named exports instead. - The 
ClientandServiceClientclasses are now deprecated. They are replaced by a singleXrpcClientclass. - The default export of the 
@atproto/apipackage has been deprecated. Use named exports instead. - The 
BskyAgenthas been deprecated. Use theAtpAgentclass instead. - The 
xrpcproperty of theAtpClientinstances has been deprecated. The instance itself should be used as the XRPC client. - The 
apiproperty of theAtpAgentandBskyAgentinstances has been deprecated. Use the instance itself instead. 
Migration
The @atproto/api package
If you were relying on the AtpBaseClient solely to perform validation, use this:
 |  | 
If you are extending the BskyAgent to perform custom session manipulation, define your own Agent subclass instead:
 |  | 
If you are monkey patching the xrpc service client to perform client-side rate limiting, you can now do this in the FetchHandler function:
 |  | 
If you configure a static fetch handler on the BskyAgent class - for example to modify the headers of every request - you can now do this by providing your own fetch function:
 |  | 
The @atproto/xrpc package
The Client and ServiceClient classes are now deprecated. If you need a lexicon based client, you should update the code to use the XrpcClient class instead.
The deprecated ServiceClient class now extends the new XrpcClient class. Because of this, the fetch FetchHandler can no longer be configured on the Client instances (including the default export of the package). If you are not relying on the fetch FetchHandler, the new changes should have no impact on your code. Beware that the deprecated classes will eventually be removed in a
future version.
Since its use has completely changed, the FetchHandler type has also completely changed. The new FetchHandler type is now a function that receives a url pathname and a RequestInit object and returns a Promise<Response>. This function is responsible for making the actual request to the server.
export type FetchHandler = (
  this: void,
  /**
   * The URL (pathname + query parameters) to make the request to, without the
   * origin. The origin (protocol, hostname, and port) must be added by this
   * {@link FetchHandler}, typically based on authentication or other factors.
   */
  url: string,
  init: RequestInit,
) => Promise<Response>
A noticeable change that has been introduced is that the uri field of the ServiceClient class has not been ported to the new XrpcClient class. It is now the responsibility of the FetchHandler to determine the full URL to make the request to. The same goes for the headers, which should now be set through the FetchHandler function.
If you do rely on the legacy Client.fetch property to perform custom logic upon request, you will need to migrate your code to use the new XrpcClient class. The XrpcClient class has a similar API to the old ServiceClient class, but with a few differences:
- The 
Client+ServiceClientduality was removed in favor of a singleXrpcClientclass. This means that:- There no longer exists a centralized lexicon registry. If you need a global lexicon registry, you can maintain one yourself using a 
new Lexicons(from@atproto/lexicon). - The 
FetchHandleris no longer a statically defined property of theClientclass. Instead, it is passed as an argument to theXrpcClientconstructor. 
 - There no longer exists a centralized lexicon registry. If you need a global lexicon registry, you can maintain one yourself using a 
 - The 
XrpcClientconstructor now requires aFetchHandlerfunction as the first argument, and an optionalLexiconinstance as the second argument. - The 
setHeaderandunsetHeadermethods were not ported to the newXrpcClientclass. If you need to set or unset headers, you should do so in theFetchHandlerfunction provided in the constructor arg. 
 |  | 
If your fetch handler does not require any "custom logic", and all you need is an XrpcClient that makes its HTTP requests towards a static service URL, the previous example can be simplified to:
import { XrpcClient } from '@atproto/xrpc'
const instance = new XrpcClient('http://my-service.com', [
  {
    lexicon: 1,
    id: 'io.example.doStuff',
    defs: {},
  },
])
If you need to add static headers to all requests, you can instead instantiate the XrpcClient as follows:
import { XrpcClient } from '@atproto/xrpc'
const instance = new XrpcClient(
  {
    service: 'http://my-service.com',
    headers: {
      'my-header': 'my-value',
    },
  },
  [
    {
      lexicon: 1,
      id: 'io.example.doStuff',
      defs: {},
    },
  ],
)
If you need the headers or service url to be dynamic, you can define them using functions:
import { XrpcClient } from '@atproto/xrpc'
const instance = new XrpcClient(
  {
    service: () => 'http://my-service.com',
    headers: {
      'my-header': () => 'my-value',
      'my-ignored-header': () => null, // ignored
    },
  },
  [
    {
      lexicon: 1,
      id: 'io.example.doStuff',
      defs: {},
    },
  ],
)