Header menu logo FSharp.ATProto

Social Actions

Follow, block, and mute users on Bluesky, with typed refs and undo support.

Domain Types

FollowRef

Returned by Bluesky.follow -- pass to unfollow or undoFollow to undo.

Field

Type

Description

Uri

AtUri

AT-URI of the follow record

BlockRef

Returned by Bluesky.block -- pass to unblock or undoBlock to undo.

Field

Type

Description

Uri

AtUri

AT-URI of the block record

LikeRef

Returned by Bluesky.like -- pass to unlike or undoLike to undo.

Field

Type

Description

Uri

AtUri

AT-URI of the like record

RepostRef

Returned by Bluesky.repost -- pass to unrepost or undoRepost to undo.

Field

Type

Description

Uri

AtUri

AT-URI of the repost record

ListBlockRef

Returned by Bluesky.blockModList -- pass to unblockModList to undo.

Field

Type

Description

Uri

AtUri

AT-URI of the list block record

UndoResult

Discriminated union returned by undo* functions indicating whether the action was reversed.

Case

Description

Undone

The record was deleted successfully

WasNotPresent

There was nothing to undo (e.g. the post was not liked)

Only unlikePost and unrepostPost can return WasNotPresent (they check viewer state). The ref-based undoLike, undoRepost, undoFollow, and undoBlock always return Undone because deleteRecord is idempotent.

Functions

Following

Function

Accepts

Returns

Description

Bluesky.follow

agent:AtpAgent target:Did / ProfileSummary / Profile

Result<FollowRef, XrpcError>

Follow a user

Bluesky.followByHandle

agent:AtpAgent identifier:string

Result<FollowRef, XrpcError>

Follow by handle or DID string (resolves automatically)

Bluesky.unfollow

agent:AtpAgent followRef:FollowRef

Result<unit, XrpcError>

Unfollow by ref

Bluesky.undoFollow

agent:AtpAgent followRef:FollowRef

Result<UndoResult, XrpcError>

Unfollow by ref, returning UndoResult

taskResult {
    // Follow using a profile's DID
    let! followRef = Bluesky.follow agent profile

    // Or by handle string
    let! followRef2 = Bluesky.followByHandle agent "alice.bsky.social"

    // Undo
    do! Bluesky.unfollow agent followRef
}

Blocking

Function

Accepts

Returns

Description

Bluesky.block

agent:AtpAgent target:Did / ProfileSummary / Profile

Result<BlockRef, XrpcError>

Block a user

Bluesky.blockByHandle

agent:AtpAgent identifier:string

Result<BlockRef, XrpcError>

Block by handle or DID string (resolves automatically)

Bluesky.unblock

agent:AtpAgent blockRef:BlockRef

Result<unit, XrpcError>

Unblock by ref

Bluesky.undoBlock

agent:AtpAgent blockRef:BlockRef

Result<UndoResult, XrpcError>

Unblock by ref, returning UndoResult

Bluesky.blockModList

agent:AtpAgent listUri:AtUri

Result<ListBlockRef, XrpcError>

Block an entire moderation list

Bluesky.unblockModList

agent:AtpAgent listBlockRef:ListBlockRef

Result<unit, XrpcError>

Unblock a moderation list

taskResult {
    let! blockRef = Bluesky.block agent profile
    let! blockRef2 = Bluesky.blockByHandle agent "spammer.example.com"

    // Moderation list
    let! listBlockRef = Bluesky.blockModList agent modListUri
    do! Bluesky.unblockModList agent listBlockRef
}

Muting

Function

Accepts

Returns

Description

Bluesky.muteUser

agent:AtpAgent target:Did / ProfileSummary / Profile

Result<unit, XrpcError>

Mute an account

Bluesky.muteUserByHandle

agent:AtpAgent identifier:string

Result<unit, XrpcError>

Mute by handle or DID string

Bluesky.unmuteUser

agent:AtpAgent target:Did / ProfileSummary / Profile

Result<unit, XrpcError>

Unmute an account

Bluesky.unmuteUserByHandle

agent:AtpAgent identifier:string

Result<unit, XrpcError>

Unmute by handle or DID string

Bluesky.muteModList

agent:AtpAgent listUri:AtUri

Result<unit, XrpcError>

Mute all accounts on a moderation list

Bluesky.unmuteModList

agent:AtpAgent listUri:AtUri

Result<unit, XrpcError>

Unmute a moderation list

Bluesky.muteThread

agent:AtpAgent root:TimelinePost / PostRef / AtUri

Result<unit, XrpcError>

Mute a thread

Bluesky.unmuteThread

agent:AtpAgent root:TimelinePost / PostRef / AtUri

Result<unit, XrpcError>

Unmute a thread

taskResult {
    do! Bluesky.muteUser agent profile
    do! Bluesky.muteUserByHandle agent "noisy.bsky.social"
    do! Bluesky.muteThread agent post
    do! Bluesky.muteModList agent modListUri
}

Generic Undo

Function

Accepts

Returns

Description

Bluesky.undo

agent:AtpAgent ref:LikeRef / RepostRef / FollowRef / BlockRef / ListBlockRef

Result<UndoResult, XrpcError>

Delete any ref type via SRTP

taskResult {
    let! likeRef = Bluesky.like agent post
    let! followRef = Bluesky.follow agent profile

    // Generic undo works with any ref type
    let! _ = Bluesky.undo agent likeRef
    let! _ = Bluesky.undo agent followRef
    ()
}

SRTP Polymorphism

Social action functions accept multiple types via SRTP:

taskResult {
    let! profile = Bluesky.getProfile agent aliceHandle

    // Pass the Profile directly -- no need to extract .Did
    let! followRef = Bluesky.follow agent profile
    do! Bluesky.muteUser agent profile

    // Pass a TimelinePost directly to mute its thread
    let! page = Bluesky.getTimeline agent (Some 1L) None
    do! Bluesky.muteThread agent page.Items.Head.Post
}
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.ATProto
namespace FSharp.ATProto.Syntax
namespace FSharp.ATProto.Core
namespace FSharp.ATProto.Bluesky
val agent: AtpAgent
module Unchecked from Microsoft.FSharp.Core.Operators
val defaultof<'T> : 'T
Multiple items
module AtpAgent from FSharp.ATProto.Core
<summary> Functions for creating and authenticating <see cref="AtpAgent" /> instances. </summary>

--------------------
type AtpAgent = { HttpClient: HttpClient mutable BaseUrl: Uri mutable Session: AtpSession option ExtraHeaders: (string * string) list AuthenticateRequest: (HttpRequestMessage -> unit) option RefreshAuthentication: (unit -> Task<Result<unit,XrpcError>>) option OnSessionChanged: (unit -> unit) option }
<summary> Client agent for communicating with an AT Protocol Personal Data Server (PDS). Holds the HTTP client, base URL, optional authenticated session, and extra headers. </summary>
<remarks> Create an agent with <see cref="AtpAgent.create" /> or <see cref="AtpAgent.createWithClient" />, then authenticate with <see cref="AtpAgent.login" />. The agent's <see cref="Session" /> field is mutable: it is updated automatically on login and token refresh. </remarks>
<example><code> let agent = AtpAgent.create "https://bsky.social" let! session = AtpAgent.login "my-handle.bsky.social" "app-password-here" agent </code></example>
val profile: Profile
Multiple items
module Profile from FSharp.ATProto.Bluesky

--------------------
type Profile = { Did: Did Handle: Handle DisplayName: string Description: string Avatar: string option Banner: string option PostsCount: int64 FollowersCount: int64 FollowsCount: int64 IsFollowing: bool ... }
<summary> A full user profile with engagement counts and relationship state. Maps from <c>ProfileViewDetailed</c>. </summary>
val post: TimelinePost
Multiple items
module TimelinePost from FSharp.ATProto.Bluesky

--------------------
type TimelinePost = { Uri: AtUri Cid: Cid Author: ProfileSummary Text: string Facets: Facet list LikeCount: int64 RepostCount: int64 ReplyCount: int64 QuoteCount: int64 IndexedAt: DateTimeOffset ... }
<summary> A post with engagement counts and viewer state, used in feeds and timelines. Maps from <c>PostView</c>. </summary>
val modListUri: AtUri
Multiple items
module AtUri from FSharp.ATProto.Syntax
<summary> Functions for creating, validating, and extracting data from <see cref="AtUri" /> values. </summary>

--------------------
type AtUri = private | AtUri of string override ToString: unit -> string
<summary> An AT-URI that identifies a resource in the AT Protocol network. AT-URIs use the scheme <c>at://</c> followed by an authority (DID or handle), an optional collection (NSID), and an optional record key. Format: <c>at://&lt;authority&gt;[/&lt;collection&gt;[/&lt;rkey&gt;]]</c>. Maximum length is 8192 characters. </summary>
<remarks> See the AT Protocol specification: https://atproto.com/specs/at-uri-scheme </remarks>
val aliceHandle: Handle
Multiple items
module Handle from FSharp.ATProto.Syntax
<summary> Functions for creating, validating, and extracting data from <see cref="Handle" /> values. </summary>

--------------------
type Handle = private | Handle of string override ToString: unit -> string
<summary> A handle (domain name) used as a human-readable identifier in the AT Protocol. Handles are DNS-based names (e.g. <c>my-handle.bsky.social</c>) that resolve to a <see cref="Did" />. They must be valid domain names with at least two segments and a maximum length of 253 characters. </summary>
<remarks> See the AT Protocol specification: https://atproto.com/specs/handle </remarks>
val taskResult: TaskResultBuilder
val followRef: FollowRef
module Bluesky from FSharp.ATProto.Bluesky
<summary> High-level convenience methods for common Bluesky operations: posting, replying, liking, reposting, following, blocking, uploading blobs, and deleting records. All methods require an authenticated <see cref="AtpAgent" />. </summary>
val follow: agent: AtpAgent -> target: 'a -> System.Threading.Tasks.Task<Result<FollowRef,XrpcError>> (requires member ToDid)
<summary> Follow a user. Accepts a <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" /> directly. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="target">The user to follow — a <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" />.</param>
<returns>A <see cref="FollowRef" /> on success, or an <see cref="XrpcError" />. Pass the <c>FollowRef</c> to <see cref="unfollow" /> to undo.</returns>
val followRef2: FollowRef
val followByHandle: agent: AtpAgent -> identifier: string -> System.Threading.Tasks.Task<Result<FollowRef,XrpcError>>
<summary> Follow a user by handle string. The handle is resolved to a DID, then the follow is created. Also accepts a DID string directly (if it starts with <c>did:</c>, it is parsed as a DID). </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="identifier">A handle (e.g., <c>my-handle.bsky.social</c>) or DID string (e.g., <c>did:plc:abc123</c>).</param>
<returns>A <see cref="FollowRef" /> on success, or an <see cref="XrpcError" />. Pass the <c>FollowRef</c> to <see cref="unfollow" /> to undo.</returns>
<remarks> For type-safe usage when you already have a <see cref="Did" />, use <see cref="follow" /> instead. </remarks>
val unfollow: agent: AtpAgent -> followRef: FollowRef -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Unfollow a user by deleting the follow record. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="followRef">The <see cref="FollowRef" /> returned by <see cref="follow" />.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
val blockRef: BlockRef
val block: agent: AtpAgent -> target: 'a -> System.Threading.Tasks.Task<Result<BlockRef,XrpcError>> (requires member ToDid)
<summary> Block a user. Accepts a <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" /> directly. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="target">The user to block — a <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" />.</param>
<returns>A <see cref="BlockRef" /> on success, or an <see cref="XrpcError" />. Pass the <c>BlockRef</c> to <see cref="unblock" /> to undo.</returns>
val blockRef2: BlockRef
val blockByHandle: agent: AtpAgent -> identifier: string -> System.Threading.Tasks.Task<Result<BlockRef,XrpcError>>
<summary> Block a user by handle string. The handle is resolved to a DID, then the block is created. Also accepts a DID string directly (if it starts with <c>did:</c>, it is parsed as a DID). </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="identifier">A handle (e.g., <c>my-handle.bsky.social</c>) or DID string (e.g., <c>did:plc:abc123</c>).</param>
<returns>A <see cref="BlockRef" /> on success, or an <see cref="XrpcError" />. Pass the <c>BlockRef</c> to <see cref="unblock" /> to undo.</returns>
<remarks> For type-safe usage when you already have a <see cref="Did" />, use <see cref="block" /> instead. </remarks>
val listBlockRef: ListBlockRef
val blockModList: agent: AtpAgent -> listUri: AtUri -> System.Threading.Tasks.Task<Result<ListBlockRef,XrpcError>>
<summary> Block an entire moderation list. Creates a <c>app.bsky.graph.listblock</c> record. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listUri">The AT-URI of the moderation list to block.</param>
<returns>A <see cref="ListBlockRef" /> on success, or an <see cref="XrpcError" />. Pass the <c>ListBlockRef</c> to <see cref="unblockModList" /> to undo.</returns>
val unblockModList: agent: AtpAgent -> listBlockRef: ListBlockRef -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Unblock a previously blocked moderation list by deleting the list block record. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listBlockRef">The <see cref="ListBlockRef" /> returned by <see cref="blockModList" />.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
val muteUser: agent: AtpAgent -> target: 'a -> System.Threading.Tasks.Task<Result<unit,XrpcError>> (requires member ToDid)
<summary> Mute an account. Accepts a <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" /> directly. Muted accounts are hidden from your feeds but not blocked. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="target">The user to mute — a <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" />.</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
val muteUserByHandle: agent: AtpAgent -> identifier: string -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Mute an account by handle string. The handle is resolved to a DID, then the mute is created. Also accepts a DID string directly (if it starts with <c>did:</c>, it is parsed as a DID). </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="identifier">A handle (e.g., <c>my-handle.bsky.social</c>) or DID string (e.g., <c>did:plc:abc123</c>).</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
<remarks> For type-safe usage when you already have a <see cref="Did" />, use <see cref="muteUser" /> instead. </remarks>
val muteThread: agent: AtpAgent -> root: 'a -> System.Threading.Tasks.Task<Result<unit,XrpcError>> (requires member ToAtUri)
<summary> Mute a thread. Posts in the muted thread are hidden from your notifications. Accepts an <see cref="AtUri" />, <see cref="PostRef" />, or <see cref="TimelinePost" />. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="root">The thread root post (an <see cref="AtUri" />, <see cref="PostRef" />, or <see cref="TimelinePost" />).</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
val muteModList: agent: AtpAgent -> listUri: AtUri -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Mute an entire moderation list. All accounts on the list are muted. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listUri">The AT-URI of the moderation list to mute.</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
val likeRef: LikeRef
val like: agent: AtpAgent -> target: 'a -> System.Threading.Tasks.Task<Result<LikeRef,XrpcError>> (requires member ToPostRef)
<summary> Like a post or other record. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="target">A <see cref="PostRef" /> or <see cref="TimelinePost" /> identifying the record to like.</param>
<returns>A <see cref="LikeRef" /> on success, or an <see cref="XrpcError" />. Pass the <c>LikeRef</c> to <see cref="unlike" /> to undo.</returns>
val undo: agent: AtpAgent -> ref: 'a -> System.Threading.Tasks.Task<Result<UndoResult,XrpcError>> (requires member UndoUri)
<summary> Generic undo: delete any ref type (<see cref="LikeRef" />, <see cref="RepostRef" />, <see cref="FollowRef" />, or <see cref="BlockRef" />). Dispatches to the correct typed undo function via SRTP. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="ref">Any ref type with an AT-URI (LikeRef, RepostRef, FollowRef, or BlockRef).</param>
<returns><c>Ok Undone</c> on success, or an <see cref="XrpcError" />. Note: the AT Protocol's deleteRecord is idempotent, so this always returns <c>Undone</c> even if the record was already deleted. Only target-based functions (<see cref="unlikePost" />/<see cref="unrepostPost" />) can return <c>WasNotPresent</c>. </returns>
<example><code> let! likeRef = Bluesky.like agent postRef let! result = Bluesky.undo agent likeRef // works with any ref type </code></example>
val getProfile: agent: AtpAgent -> actor: 'a -> System.Threading.Tasks.Task<Result<Profile,XrpcError>> (requires member ToActorString)
<summary> Get a user's profile. Accepts a <see cref="Handle" />, <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" />. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="actor">A <see cref="Handle" />, <see cref="Did" />, <see cref="ProfileSummary" />, or <see cref="Profile" />.</param>
<returns>A <see cref="Profile" /> on success, or an <see cref="XrpcError" />.</returns>
val page: Page<FeedItem>
val getTimeline: agent: AtpAgent -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<Page<FeedItem>,XrpcError>>
<summary> Get the authenticated user's home timeline. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="limit">Maximum number of posts to return (optional, pass <c>None</c> for server default).</param>
<param name="cursor">Pagination cursor from a previous response (optional, pass <c>None</c> to start from the beginning).</param>
<returns>A page of <see cref="FeedItem" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
Page.Items: FeedItem list
property List.Head: FeedItem with get
FeedItem.Post: TimelinePost

Type something to start searching.