Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@atproto/api

bluesky-social203.8kMIT0.15.19TypeScript support: included

Client library for atproto and Bluesky

atproto, bluesky, api

readme

ATP API

This API is a client for ATProtocol servers. It communicates using HTTP. It includes:

  • ✔️ APIs for ATProto and Bluesky.
  • ✔️ Validation and complete typescript types.
  • ✔️ Session management.
  • ✔️ A RichText library.

Getting started

First install the package:

yarn add @atproto/api

Then in your application:

import { AtpAgent } from '@atproto/api'

const agent = new AtpAgent({ service: 'https://example.com' })

Usage

Session management

You'll need an authenticated session for most API calls. There are two ways to manage sessions:

  1. App password based session management
  2. OAuth based session management

App password based session management

Username / password based authentication can be performed using the AtpAgent class.

[!CAUTION]

This method is deprecated in favor of OAuth based session management. It is recommended to use OAuth based session management (through the @atproto/oauth-client-* packages).

import { AtpAgent, AtpSessionEvent, AtpSessionData } from '@atproto/api'

// configure connection to the server, without account authentication
const agent = new AtpAgent({
  service: 'https://example.com',
  persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) => {
    // store the session-data for reuse
  },
})

// Change the agent state to an authenticated state either by:

// 1) creating a new account on the server.
await agent.createAccount({
  email: 'alice@mail.com',
  password: 'hunter2',
  handle: 'alice.example.com',
  inviteCode: 'some-code-12345-abcde',
})

// 2) if an existing session was securely stored previously, then reuse that to resume the session.
await agent.resumeSession(savedSessionData)

// 3) if no old session was available, create a new one by logging in with password (App Password)
await agent.login({
  identifier: 'alice@mail.com',
  password: 'hunter2',
})

OAuth based session management

Depending on the environment used by your application, different OAuth clients are available:

Every @atproto/oauth-client-* implementation has a different way to obtain an OAuthSession instance that can be used to instantiate an Agent (from @atproto/api). Here is an example restoring a previously saved session:

import { Agent } from '@atproto/api'
import { OAuthClient } from '@atproto/oauth-client'

const oauthClient = new OAuthClient({
  // ...
})

const oauthSession = await oauthClient.restore('did:plc:123')

// Instantiate the api Agent using an OAuthSession
const agent = new Agent(oauthSession)

API calls

The agent includes methods for many common operations, including:

// The DID of the user currently authenticated (or undefined)
agent.did
agent.accountDid // Throws if the user is not authenticated

// Feeds and content
await agent.getTimeline(params, opts)
await agent.getAuthorFeed(params, opts)
await agent.getPostThread(params, opts)
await agent.getPost(params)
await agent.getPosts(params, opts)
await agent.getLikes(params, opts)
await agent.getRepostedBy(params, opts)
await agent.post(record)
await agent.deletePost(postUri)
await agent.like(uri, cid)
await agent.deleteLike(likeUri)
await agent.repost(uri, cid)
await agent.deleteRepost(repostUri)
await agent.uploadBlob(data, opts)

// Social graph
await agent.getFollows(params, opts)
await agent.getFollowers(params, opts)
await agent.follow(did)
await agent.deleteFollow(followUri)

// Actors
await agent.getProfile(params, opts)
await agent.upsertProfile(updateFn)
await agent.getProfiles(params, opts)
await agent.getSuggestions(params, opts)
await agent.searchActors(params, opts)
await agent.searchActorsTypeahead(params, opts)
await agent.mute(did)
await agent.unmute(did)
await agent.muteModList(listUri)
await agent.unmuteModList(listUri)
await agent.blockModList(listUri)
await agent.unblockModList(listUri)

// Notifications
await agent.listNotifications(params, opts)
await agent.countUnreadNotifications(params, opts)
await agent.updateSeenNotifications()

// Identity
await agent.resolveHandle(params, opts)
await agent.updateHandle(params, opts)

// Legacy: Session management should be performed through the SessionManager
// rather than the Agent instance.
if (agent instanceof AtpAgent) {
  // AtpAgent instances support using different sessions during their lifetime
  await agent.createAccount({ ... }) // session a
  await agent.login({ ... }) // session b
  await agent.resumeSession(savedSession) // session c
}

Validation and types

The package includes a complete types system which includes validation and type-guards. For example, to validate a post record:

import { AppBskyFeedPost } from '@atproto/api'

const post = {...}
if (AppBskyFeedPost.isRecord(post)) {
  // typescript now recognizes `post` as a AppBskyFeedPost.Record
  // however -- we still need to validate it
  const res = AppBskyFeedPost.validateRecord(post)
  if (res.success) {
    // a valid record
  } else {
    // something is wrong
    console.log(res.error)
  }
}

Rich text

Some records (ie posts) use the app.bsky.richtext lexicon. At the moment richtext is only used for links and mentions, but it will be extended over time to include bold, italic, and so on.

ℹ️ It is strongly recommended to use this package's RichText library. Javascript encodes strings in utf16 while the protocol (and most other programming environments) use utf8. Converting between the two is challenging, but RichText handles that for you.

import { RichText } from '@atproto/api'

// creating richtext
const rt = new RichText({
  text: 'Hello @alice.com, check out this link: https://example.com',
})
await rt.detectFacets(agent) // automatically detects mentions and links
const postRecord = {
  $type: 'app.bsky.feed.post',
  text: rt.text,
  facets: rt.facets,
  createdAt: new Date().toISOString(),
}

// rendering as markdown
let markdown = ''
for (const segment of rt.segments()) {
  if (segment.isLink()) {
    markdown += `[${segment.text}](${segment.link?.uri})`
  } else if (segment.isMention()) {
    markdown += `[${segment.text}](https://my-bsky-app.com/user/${segment.mention?.did})`
  } else {
    markdown += segment.text
  }
}

// calculating string lengths
const rt2 = new RichText({ text: 'Hello' })
console.log(rt2.length) // => 5
console.log(rt2.graphemeLength) // => 5
const rt3 = new RichText({ text: '👨‍👩‍👧‍👧' })
console.log(rt3.length) // => 25
console.log(rt3.graphemeLength) // => 1

Moderation

Applying the moderation system is a challenging task, but we've done our best to simplify it for you. The Moderation API helps handle a wide range of tasks, including:

  • Moderator labeling
  • User muting (including mutelists)
  • User blocking
  • Mutewords
  • Hidden posts

For more information, see the Moderation Documentation.

import { moderatePost } from '@atproto/api'

// First get the user's moderation prefs and their label definitions
// =

const prefs = await agent.getPreferences()
const labelDefs = await agent.getLabelDefinitions(prefs)

// We call the appropriate moderation function for the content
// =

const postMod = moderatePost(postView, {
  userDid: agent.session.did,
  moderationPrefs: prefs.moderationPrefs,
  labelDefs,
})

// We then use the output to decide how to affect rendering
// =

// in feeds
if (postMod.ui('contentList').filter) {
  // don't include in feeds
}
if (postMod.ui('contentList').blur) {
  // render the whole object behind a cover (use postMod.ui('contentList').blurs to explain)
  if (postMod.ui('contentList').noOverride) {
    // do not allow the cover the be removed
  }
}
if (postMod.ui('contentList').alert || postMod.ui('contentList').inform) {
  // render warnings on the post
  // find the warnings in postMod.ui('contentList').alerts and postMod.ui('contentList').informs
}

// viewed directly
if (postMod.ui('contentView').filter) {
  // don't include in feeds
}
if (postMod.ui('contentView').blur) {
  // render the whole object behind a cover (use postMod.ui('contentView').blurs to explain)
  if (postMod.ui('contentView').noOverride) {
    // do not allow the cover the be removed
  }
}
if (postMod.ui('contentView').alert || postMod.ui('contentView').inform) {
  // render warnings on the post
  // find the warnings in postMod.ui('contentView').alerts and postMod.ui('contentView').informs
}

// post embeds in all contexts
if (postMod.ui('contentMedia').blur) {
  // render the whole object behind a cover (use postMod.ui('contentMedia').blurs to explain)
  if (postMod.ui('contentMedia').noOverride) {
    // do not allow the cover the be removed
  }
}

Advanced

Advanced API calls

The methods above are convenience wrappers. It covers most but not all available methods.

The AT Protocol identifies methods and records with reverse-DNS names. You can use them on the agent as well:

const res1 = await agent.com.atproto.repo.createRecord({
  did: alice.did,
  collection: 'app.bsky.feed.post',
  record: {
    $type: 'app.bsky.feed.post',
    text: 'Hello, world!',
    createdAt: new Date().toISOString(),
  },
})
const res2 = await agent.com.atproto.repo.listRecords({
  repo: alice.did,
  collection: 'app.bsky.feed.post',
})

const res3 = await agent.app.bsky.feed.post.create(
  { repo: alice.did },
  {
    text: 'Hello, world!',
    createdAt: new Date().toISOString(),
  },
)
const res4 = await agent.app.bsky.feed.post.list({ repo: alice.did })

Non-browser configuration

If your environment doesn't have a built-in fetch implementation, you'll need to provide one. This will typically be done through a polyfill.

Bring your own fetch

If you want to provide your own fetch implementation, you can do so by instantiating the sessionManager with a custom fetch implementation:

import { AtpAgent } from '@atproto/api'

const myFetch = (input: RequestInfo | URL, init?: RequestInit) => {
  console.log('requesting', input)
  const response = await globalThis.fetch(input, init)
  console.log('got response', response)
  return response
}

const agent = new AtpAgent({
  service: 'https://example.com',
  fetch: myFetch,
})

License

This project is dual-licensed under MIT and Apache 2.0 terms:

Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.

changelog

@atproto/api

0.15.19

Patch Changes

0.15.18

Patch Changes

0.15.17

Patch Changes

0.15.16

Patch Changes

0.15.15

Patch Changes

  • #2934 7f1316748 Thanks @estrattonbailey! - Fix bug where fuzzy matching mute words was over-zealous e.g. Andor matching and/or.

  • #2934 7f1316748 Thanks @estrattonbailey! - Updates mute word matching to include a matches: MuteWordMatch[] property on the muted-word cause type returned as part of a ModerationDecision.

0.15.14

Patch Changes

0.15.13

Patch Changes

0.15.12

Patch Changes

  • #3912 a5cd018bd Thanks @estrattonbailey! - Unify getPostThreadV2 and getPostThreadHiddenV2 responses under app.bsky.unspecced.defs namespace and a single interface via threadItemPost.

0.15.11

Patch Changes

0.15.10

Patch Changes

0.15.9

Patch Changes

  • #3882 79a75bb1e Thanks @mozzius! - add a "via" field to reposts and likes allowing a reference a repost, and then give a notification when a repost is liked or reposted.

0.15.8

Patch Changes

0.15.7

Patch Changes

0.15.6

Patch Changes

0.15.5

Patch Changes

0.15.4

Patch Changes

0.15.3

Patch Changes

0.15.2

Patch Changes

0.15.1

Patch Changes

0.15.0

Minor Changes

0.14.22

Patch Changes

0.14.21

Patch Changes

0.14.20

Patch Changes

0.14.19

Patch Changes

0.14.18

Patch Changes

0.14.17

Patch Changes

0.14.16

Patch Changes

0.14.15

Patch Changes

0.14.14

Patch Changes

0.14.13

Patch Changes

  • #3651 076c2f987 Thanks @foysalit! - Add getSubjects endpoint to ozone for fetching detailed view of multiple subjects

0.14.12

Patch Changes

0.14.11

Patch Changes

0.14.10

Patch Changes

0.14.9

Patch Changes

0.14.8

Patch Changes

0.14.7

Patch Changes

0.14.6

Patch Changes

0.14.5

Patch Changes

0.14.4

Patch Changes

0.14.3

Patch Changes

0.14.2

Patch Changes

0.14.1

Patch Changes

0.14.0

Minor Changes

  • #2999 c53d943c8 Thanks @matthieusieben! - Update Lexicon derived code to better reflect data typings. In particular, Lexicon derived interfaces will now explicitly include the $type property that can be present in the data.

  • #2999 c53d943c8 Thanks @matthieusieben! - Helper functions (e.g. NS.isRecord) no longer casts the output value. Use asPredicate(NS.validateRecord) to create a predicate function that will ensure that an unknown value is indeed an NS.Record. The isX helper function's purpose is to discriminate between $typed values from unions.

Patch Changes

0.13.35

Patch Changes

0.13.34

Patch Changes

0.13.33

Patch Changes

0.13.32

Patch Changes

0.13.31

Patch Changes

0.13.30

Patch Changes

0.13.29

Patch Changes

0.13.28

Patch Changes

0.13.27

Patch Changes

0.13.26

Patch Changes

0.13.25

Patch Changes

0.13.24

Patch Changes

0.13.23

Patch Changes

0.13.22

Patch Changes

  • #3270 f22383cee Thanks @estrattonbailey! - Add support for label def aliases, deprecation notices. This provides support for the deprecated gore label until a full cleanup effort can be completed.

0.13.21

Patch Changes

0.13.20

Patch Changes

0.13.19

Patch Changes

  • #3171 ed2236220 Thanks @foysalit! - Allow moderators to optionally acknowledge all open subjects of an account when acknowledging account level reports

0.13.18

Patch Changes

0.13.17

Patch Changes

0.13.16

Patch Changes

0.13.15

Patch Changes

0.13.14

Patch Changes

0.13.13

Patch Changes

  • #2914 19e36afb2 Thanks @foysalit! - Add collections and subjectType filters to ozone's queryEvents and queryStatuses endpoints

0.13.12

Patch Changes

  • #2636 22d039a22 Thanks @foysalit! - Sets api to manage lists of strings on ozone, mostly aimed for automod configuration

0.13.11

Patch Changes

0.13.10

Patch Changes

0.13.9

Patch Changes

0.13.8

Patch Changes

0.13.7

Patch Changes

0.13.6

Patch Changes

0.13.5

Patch Changes

0.13.4

Patch Changes

0.13.3

Patch Changes

0.13.2

Patch Changes

0.13.1

Patch Changes

0.13.0

Minor Changes

  • #2483 b934b396b Thanks @matthieusieben!

    Motivation

    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.

    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 the fetch: FetchHandler property of @atproto/xrpc's Client and @atproto/api's AtpAgent classes has been removed. Previously, the fetch 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 fetchHandler property and can only be set during the instance creation. Attempting to set/get the fetch property will now result in an error.
    • The fetch() method, as well as WhatWG compliant Request and Headers constructors, must be globally available in your environment. Use a polyfill if necessary.
    • The AtpBaseClient has been removed. The AtpServiceClient has been renamed AtpBaseClient. Any code using either of these classes will need to be updated.
    • Instead of wrapping an XrpcClient in its xrpc property, the AtpBaseClient (formerly AtpServiceClient) class - created through lex-cli - now extends the XrpcClient class. This means that a client instance now passes the instanceof XrpcClient check. The xrpc property now returns the instance itself and has been deprecated.
    • setSessionPersistHandler is no longer available on the AtpAgent or BskyAgent classes. The session handler can only be set though the persistSession options of the AtpAgent constructor.
    • The new class hierarchy is as follows:
      • BskyAgent extends AtpAgent: but add no functionality (hence its deprecation).
      • AtpAgent extends Agent: adds password based session management.
      • Agent extends AtpBaseClient: this abstract class that adds syntactic sugar methods app.bsky lexicons. It also adds abstract session management methods and adds atproto specific utilities (labelers & proxy headers, cloning capability)
      • AtpBaseClient extends XrpcClient: automatically code that adds fully typed lexicon defined namespaces (instance.app.bsky.feed.getPosts()) to the XrpcClient.
      • XrpcClient is the base class.

    Non-breaking changes

    • The com.* and app.* namespaces have been made directly available to every Agent instances.

    Deprecations

    • The default export of the @atproto/xrpc package has been deprecated. Use named exports instead.
    • The Client and ServiceClient classes are now deprecated. They are replaced by a single XrpcClient class.
    • The default export of the @atproto/api package has been deprecated. Use named exports instead.
    • The BskyAgent has been deprecated. Use the AtpAgent class instead.
    • The xrpc property of the AtpClient instances has been deprecated. The instance itself should be used as the XRPC client.
    • The api property of the AtpAgent and BskyAgent instances 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:

    Before
    After
    import { AtpBaseClient, ComAtprotoSyncSubscribeRepos } from "@atproto/api";
    
    const baseClient = new AtpBaseClient();
    
    baseClient.xrpc.lex.assertValidXrpcMessage("io.example.doStuff", {
      // ...
    });
    import { lexicons } from "@atproto/api";
    
    lexicons.assertValidXrpcMessage("io.example.doStuff", {
      // ...
    });

    If you are extending the BskyAgent to perform custom session manipulation, define your own Agent subclass instead:

    Before
    After
    import { BskyAgent } from "@atproto/api";
    
    class MyAgent extends BskyAgent {
      private accessToken?: string;
    
      async createOrRefreshSession(identifier: string, password: string) {
        // custom logic here
    
        this.accessToken = "my-access-jwt";
      }
    
      async doStuff() {
        return this.call("io.example.doStuff", {
          headers: {
            Authorization: this.accessToken && `Bearer ${this.accessToken}`,
          },
        });
      }
    }
    import { Agent } from "@atproto/api";
    
    class MyAgent extends Agent {
      private accessToken?: string;
      public did?: string;
    
      constructor(private readonly service: string | URL) {
        super({
          service,
          headers: {
            Authorization: () =>
              this.accessToken ? `Bearer ${this.accessToken}` : null,
          },
        });
      }
    
      clone(): MyAgent {
        const agent = new MyAgent(this.service);
        agent.accessToken = this.accessToken;
        agent.did = this.did;
        return this.copyInto(agent);
      }
    
      async createOrRefreshSession(identifier: string, password: string) {
        // custom logic here
    
        this.did = "did:example:123";
        this.accessToken = "my-access-jwt";
      }
    }

    If you are monkey patching the xrpc service client to perform client-side rate limiting, you can now do this in the FetchHandler function:

    Before
    After
    import { BskyAgent } from "@atproto/api";
    import { RateLimitThreshold } from "rate-limit-threshold";
    
    const agent = new BskyAgent();
    const limiter = new RateLimitThreshold(3000, 300_000);
    
    const origCall = agent.api.xrpc.call;
    agent.api.xrpc.call = async function (...args) {
      await limiter.wait();
      return origCall.call(this, ...args);
    };
    import { AtpAgent } from "@atproto/api";
    import { RateLimitThreshold } from "rate-limit-threshold";
    
    class LimitedAtpAgent extends AtpAgent {
      constructor(options: AtpAgentOptions) {
        const fetch: typeof globalThis.fetch = options.fetch ?? globalThis.fetch;
        const limiter = new RateLimitThreshold(3000, 300_000);
    
        super({
          ...options,
          fetch: async (...args) => {
            await limiter.wait();
            return fetch(...args);
          },
        });
      }
    }

    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:

    Before
    After
    import { BskyAgent, defaultFetchHandler } from "@atproto/api";
    
    BskyAgent.configure({
      fetch: async (httpUri, httpMethod, httpHeaders, httpReqBody) => {
        const ua = httpHeaders["User-Agent"];
    
        httpHeaders["User-Agent"] = ua ? `${ua} ${userAgent}` : userAgent;
    
        return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody);
      },
    });
    import { AtpAgent } from "@atproto/api";
    
    class MyAtpAgent extends AtpAgent {
      constructor(options: AtpAgentOptions) {
        const fetch = options.fetch ?? globalThis.fetch;
    
        super({
          ...options,
          fetch: async (url, init) => {
            const headers = new Headers(init.headers);
    
            const ua = headersList.get("User-Agent");
            headersList.set("User-Agent", ua ? `${ua} ${userAgent}` : userAgent);
    
            return fetch(url, { ...init, headers });
          },
        });
      }
    }
    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 + ServiceClient duality was removed in favor of a single XrpcClient class. 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 FetchHandler is no longer a statically defined property of the Client class. Instead, it is passed as an argument to the XrpcClient constructor.
    • The XrpcClient constructor now requires a FetchHandler function as the first argument, and an optional Lexicon instance as the second argument.

    • The setHeader and unsetHeader methods were not ported to the new XrpcClient class. If you need to set or unset headers, you should do so in the FetchHandler function provided in the constructor arg.
    Before
    After
    import client, { defaultFetchHandler } from "@atproto/xrpc";
    
    client.fetch = function (
      httpUri: string,
      httpMethod: string,
      httpHeaders: Headers,
      httpReqBody: unknown,
    ) {
      // Custom logic here
      return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody);
    };
    
    client.addLexicon({
      lexicon: 1,
      id: "io.example.doStuff",
      defs: {},
    });
    
    const instance = client.service("http://my-service.com");
    
    instance.setHeader("my-header", "my-value");
    
    await instance.call("io.example.doStuff");
    import { XrpcClient } from "@atproto/xrpc";
    
    const instance = new XrpcClient(
      async (url, init) => {
        const headers = new Headers(init.headers);
    
        headers.set("my-header", "my-value");
    
        // Custom logic here
    
        const fullUrl = new URL(url, "http://my-service.com");
    
        return fetch(fullUrl, { ...init, headers });
      },
      [
        {
          lexicon: 1,
          id: "io.example.doStuff",
          defs: {},
        },
      ],
    );
    
    await instance.call("io.example.doStuff");

    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: {},
        },
      ],
    );
  • #2483 b934b396b Thanks @matthieusieben! - Add the ability to use fetch() compatible BodyInit body when making XRPC calls.

Patch Changes

0.12.29

Patch Changes

0.12.28

Patch Changes

0.12.27

Patch Changes

0.12.26

Patch Changes

  • #2276 77c5306d2 Thanks @estrattonbailey! - Updates muted words lexicons to include new attributes id, actorTarget, and expiresAt. Adds and updates methods in API SDK for better management of muted words.

0.12.25

Patch Changes

0.12.24

Patch Changes

0.12.23

Patch Changes

  • #2492 bc861a2c2 Thanks @pfrazee! - Added bsky app state preference and improved protections against race conditions in preferences sdk

0.12.22

Patch Changes

0.12.21

Patch Changes

0.12.20

Patch Changes

0.12.19

Patch Changes

0.12.18

Patch Changes

  • #2557 58abcbd8b Thanks @estrattonbailey! - Adds "social proof": knowFollowers to ViewerState for ProfileViewDetailed views and app.bsky.graph.getKnownFollowers method for listing known followers of a given user.

0.12.17

Patch Changes

  • #2426 2b21b5be2 Thanks @foysalit! - Add com.atproto.admin.searchAccounts lexicon to allow searching for accounts using email address

0.12.16

Patch Changes

0.12.15

Patch Changes

  • #2531 255d5ea1f Thanks @dholms! - Account deactivation. Current hosting status returned on session routes.

0.12.14

Patch Changes

0.12.13

Patch Changes

0.12.12

Patch Changes

  • #2442 1f560f021 Thanks @foysalit! - Add com.atproto.label.queryLabels endpoint on appview and allow viewing external labels through ozone

0.12.11

Patch Changes

0.12.10

Patch Changes

0.12.9

Patch Changes

0.12.8

Patch Changes

0.12.7

Patch Changes

0.12.6

Patch Changes

  • #2427 b9b7c5821 Thanks @estrattonbailey! - Introduces V2 of saved feeds preferences. V2 and v1 prefs are incompatible. v1 methods and preference objects are retained for backwards compatability, but are considered deprecated. Developers should immediately migrate to v2 interfaces.

0.12.5

Patch Changes

0.12.4

Patch Changes

0.12.3

Patch Changes

0.12.2

Patch Changes

0.12.1

Patch Changes

  • #2342 eb7668c07 Thanks @estrattonbailey! - Adds the associated property to profile and profile-basic views, bringing them in line with profile-detailed views.

0.12.0

Minor Changes

Patch Changes

0.11.2

Patch Changes

  • #2328 7dd9941b7 Thanks @estrattonbailey! - Remove unecessary escapes from regex, which was causing a minification error when bundled in React Native.

0.11.1

Patch Changes

0.11.0

Minor Changes

  • #2302 4eaadc0ac Thanks @dholms! - - Breaking changes
    • Redesigned the moderate* APIs which now output a ModerationUI object.
    • agent.getPreferences() output object BskyPreferences has been modified.
    • Moved Ozone routes from com.atproto.admin to tools.ozone namespace.
    • Additions
      • Added support for labeler configuration in Agent.configure() and agent.configureLabelerHeader().
      • Added agent.addLabeler() and agent.removeLabeler() preference methods.
      • Muted words and hidden posts are now handled in the moderate* APIs.
      • Added agent.getLabelers() and agent.getLabelDefinitions().
      • Added agent.configureProxyHeader() and withProxy() methods to support remote service proxying behaviors.

Patch Changes

0.10.5

Patch Changes

  • #2279 192223f12 Thanks @gaearon! - Change Following feed prefs to only show replies from people you follow by default

0.10.4

Patch Changes

0.10.3

Patch Changes

0.10.2

Patch Changes

0.10.1

Patch Changes

0.10.0

Minor Changes

Patch Changes

0.9.8

Patch Changes

  • #2192 f79cc6339 Thanks @foysalit! - Tag event on moderation subjects and allow filtering events and subjects by tags

0.9.7

Patch Changes

0.9.6

Patch Changes

  • #2124 e4ec7af03 Thanks @foysalit! - Allow filtering for comment, label, report type and date range on queryModerationEvents endpoint.

0.9.5

Patch Changes

0.9.4

Patch Changes

0.9.3

Patch Changes

0.9.2

Patch Changes

0.9.1

Patch Changes

0.9.0

Minor Changes

Patch Changes

0.8.0

Minor Changes

  • #2010 14067733 Thanks @estrattonbailey! - Improve resumeSession event emission. It will no longer double emit when some requests fail, and the create-failed event has been replaced by expired where appropriate, and with a new event network-error where appropriate or an unknown error occurs.

0.7.4

Patch Changes

0.7.3

Patch Changes

0.7.2

Patch Changes

0.7.1

Patch Changes

0.7.0

Minor Changes

0.6.24

Patch Changes

0.6.23

Patch Changes

0.6.22

Patch Changes

0.6.21

Patch Changes

0.6.20

Patch Changes

0.6.19

Patch Changes

0.6.18

Patch Changes

0.6.17

Patch Changes

0.6.16

Patch Changes

0.6.15

Patch Changes

0.6.14

Patch Changes

0.6.13

Patch Changes

  • #1553 3877210e Thanks @estrattonbailey! - Adds a new method app.bsky.graph.getSuggestedFollowsByActor. This method returns suggested follows for a given actor based on their likes and follows.