Header menu logo FSharp.ATProto

Profiles

Fetch, search, and update Bluesky user profiles.

Domain Types

Profile

A full user profile with engagement counts and relationship state, returned by getProfile and getProfiles.

Field

Type

Description

Did

Did

Decentralized identifier

Handle

Handle

User handle (e.g. alice.bsky.social)

DisplayName

string

Display name (defaults to "")

Description

string

Bio text (defaults to "")

Avatar

string option

Avatar image URL

Banner

string option

Banner image URL

PostsCount

int64

Total number of posts

FollowersCount

int64

Number of followers

FollowsCount

int64

Number of accounts followed

IsFollowing

bool

Whether you follow this user

IsFollowedBy

bool

Whether this user follows you

IsBlocking

bool

Whether you are blocking this user

IsBlockedBy

bool

Whether this user is blocking you

IsMuted

bool

Whether you have muted this user

ProfileSummary

A lightweight profile used in feeds, notifications, search results, and follower lists.

Field

Type

Description

Did

Did

Decentralized identifier

Handle

Handle

User handle

DisplayName

string

Display name (defaults to "")

Avatar

string option

Avatar image URL

Pass a ProfileSummary to getProfile if you need the full Profile with counts and relationship flags.

Functions

Reading Profiles

Function

Accepts

Returns

Description

Bluesky.getProfile

agent:AtpAgent actor:Handle / Did / ProfileSummary / Profile

Result<Profile, XrpcError>

Get a single full profile

Bluesky.getProfiles

agent:AtpAgent actors:Did list

Result<Profile list, XrpcError>

Get up to 25 profiles in one request

Bluesky.getFollowers

agent:AtpAgent actor:Handle / Did / ProfileSummary / Profile limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

List an actor's followers

Bluesky.getFollows

agent:AtpAgent actor:Handle / Did / ProfileSummary / Profile limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

List accounts an actor follows

Bluesky.getSuggestedFollows

agent:AtpAgent actor:Handle / Did / ProfileSummary / Profile

Result<ProfileSummary list, XrpcError>

Suggested follows based on an actor

Bluesky.getSuggestions

agent:AtpAgent limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

General account suggestions

Bluesky.searchActors

agent:AtpAgent query:string limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

Search users by name, handle, or bio

Bluesky.searchActorsTypeahead

agent:AtpAgent query:string limit:int64 option

Result<ProfileSummary list, XrpcError>

Lightweight typeahead search (no pagination)

Bluesky.getBlocks

agent:AtpAgent limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

Get blocked users (paginated)

Bluesky.getMutes

agent:AtpAgent limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

Get muted users (paginated)

Bluesky.getRelationships

agent:AtpAgent did:Did others:Did list option

Result<Relationship list, XrpcError>

Get relationship details between users

Bluesky.getKnownFollowers

agent:AtpAgent actor:Handle / Did / ProfileSummary / Profile limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

Get followers you also follow (SRTP)

Bluesky.getLists

agent:AtpAgent actor:Handle / Did / ProfileSummary / Profile limit:int64 option cursor:string option

Result<Page<ListView>, XrpcError>

Get user's lists (SRTP)

Relationship

Field

Type

Description

Did

Did

The other user's DID

Following

AtUri option

AT-URI of follow record if you follow them

FollowedBy

AtUri option

AT-URI of follow record if they follow you

taskResult {
    let! profile = Bluesky.getProfile agent aliceHandle
    printfn "%s: %d followers" profile.DisplayName profile.FollowersCount

    let! followers = Bluesky.getFollowers agent profile (Some 50L) None
    for f in followers.Items do
        printfn "  @%s" (Handle.value f.Handle)
}

Engagement Info

Function

Accepts

Returns

Description

Bluesky.getLikes

agent:AtpAgent target:TimelinePost / PostRef / AtUri limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

Who liked a post

Bluesky.getRepostedBy

agent:AtpAgent target:TimelinePost / PostRef / AtUri limit:int64 option cursor:string option

Result<Page<ProfileSummary>, XrpcError>

Who reposted a post

taskResult {
    let! likers = Bluesky.getLikes agent post (Some 50L) None
    for p in likers.Items do
        printfn "@%s liked this" (Handle.value p.Handle)
}

Writing

Function

Accepts

Returns

Description

Bluesky.updateProfile

agent:AtpAgent transform:(Profile -> Profile)

Result<unit, XrpcError>

Transform the profile record with optimistic concurrency

Bluesky.setDisplayName

agent:AtpAgent name:string option

Result<unit, XrpcError>

Set or clear display name (auto-retries on conflict)

Bluesky.setDescription

agent:AtpAgent description:string option

Result<unit, XrpcError>

Set or clear bio (auto-retries on conflict)

Bluesky.setAvatar

agent:AtpAgent avatar:(byte[] * ImageMime) option

Result<unit, XrpcError>

Upload and set avatar, or clear with None

Bluesky.setBanner

agent:AtpAgent banner:(byte[] * ImageMime) option

Result<unit, XrpcError>

Upload and set banner, or clear with None

Bluesky.upsertProfile

agent:AtpAgent updateFn:(Profile option -> Profile)

Result<unit, XrpcError>

Low-level read-modify-write with CAS retry

Bluesky.updateHandle

agent:AtpAgent handle:Handle

Result<unit, XrpcError>

Change the authenticated user's handle

Field-specific setters (setDisplayName, setDescription, setAvatar, setBanner) auto-retry once on conflict -- safe because they touch a single field. updateProfile does not retry; the caller controls the transform.

taskResult {
    // Simple field setters
    do! Bluesky.setDisplayName agent (Some "New Display Name")
    do! Bluesky.setDescription agent (Some "F# developer | AT Protocol enthusiast")

    // Set avatar from file
    let avatarBytes = System.IO.File.ReadAllBytes "avatar.jpg"
    do! Bluesky.setAvatar agent (Some (avatarBytes, Jpeg))

    // Full transform for multiple fields at once
    do! Bluesky.updateProfile agent (fun p ->
        { p with DisplayName = Some "Updated Name"
                 Description = Some "Updated bio" })
}

Note: updateProfile and upsertProfile operate on the raw AppBskyActor.Profile.Profile type (all Option fields), not the convenience Profile domain type.

SRTP Polymorphism

Read functions that take an actor accept multiple types via SRTP:

taskResult {
    let! profile = Bluesky.getProfile agent aliceHandle

    // Pass the Profile directly to get followers
    let! followers = Bluesky.getFollowers agent profile (Some 50L) None

    // Pass a ProfileSummary from the result
    let first = followers.Items.Head
    let! fullProfile = Bluesky.getProfile agent first
    ()
}
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 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 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 profile: Profile
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 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 printfn: format: Printf.TextWriterFormat<'T> -> 'T
Profile.DisplayName: string
Profile.FollowersCount: int64
val followers: Page<ProfileSummary>
val getFollowers: agent: AtpAgent -> actor: 'a -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<Page<ProfileSummary>,XrpcError>> (requires member ToActorString)
<summary> Get the followers of an actor. 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>
<param name="limit">Maximum number of followers to return (optional).</param>
<param name="cursor">Pagination cursor from a previous response (optional).</param>
<returns>A page of <see cref="ProfileSummary" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val f: ProfileSummary
Page.Items: ProfileSummary list
val value: Handle -> string
<summary> Extract the string representation of a handle. </summary>
<param name="handle">The handle to extract the value from.</param>
<returns>The full handle string (e.g. <c>"my-handle.bsky.social"</c>).</returns>
ProfileSummary.Handle: Handle
val likers: Page<ProfileSummary>
val getLikes: agent: AtpAgent -> target: 'a -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<Page<ProfileSummary>,XrpcError>> (requires member ToAtUri)
<summary> Get the accounts that have liked a specific post. Accepts an <see cref="AtUri" />, <see cref="PostRef" />, or <see cref="TimelinePost" />. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="target">The post (an <see cref="AtUri" />, <see cref="PostRef" />, or <see cref="TimelinePost" />).</param>
<param name="limit">Maximum number of likes to return (optional).</param>
<param name="cursor">Pagination cursor from a previous response (optional).</param>
<returns>A page of <see cref="ProfileSummary" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
val p: ProfileSummary
val setDisplayName: agent: AtpAgent -> name: string option -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary>Set or clear the authenticated user's display name. Retries once on conflict.</summary>
val setDescription: agent: AtpAgent -> description: string option -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary>Set or clear the authenticated user's bio/description. Retries once on conflict.</summary>
val avatarBytes: byte array
namespace System
namespace System.IO
type File = static member AppendAllBytes: path: string * bytes: byte array -> unit + 1 overload static member AppendAllBytesAsync: path: string * bytes: byte array * ?cancellationToken: CancellationToken -> Task + 1 overload static member AppendAllLines: path: string * contents: IEnumerable<string> -> unit + 1 overload static member AppendAllLinesAsync: path: string * contents: IEnumerable<string> * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 1 overload static member AppendAllText: path: string * contents: ReadOnlySpan<char> -> unit + 3 overloads static member AppendAllTextAsync: path: string * contents: ReadOnlyMemory<char> * encoding: Encoding * ?cancellationToken: CancellationToken -> Task + 3 overloads static member AppendText: path: string -> StreamWriter static member Copy: sourceFileName: string * destFileName: string -> unit + 1 overload static member Create: path: string -> FileStream + 2 overloads static member CreateSymbolicLink: path: string * pathToTarget: string -> FileSystemInfo ...
<summary>Provides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of <see cref="T:System.IO.FileStream" /> objects.</summary>
System.IO.File.ReadAllBytes(path: string) : byte array
val setAvatar: agent: AtpAgent -> avatar: (byte array * ImageMime) option -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Set or clear the authenticated user's avatar image. Uploads the blob first, then updates the profile record. Retries once on conflict. Pass <c>None</c> to clear the avatar. </summary>
union case ImageMime.Jpeg: ImageMime
val updateProfile: agent: AtpAgent -> transform: (AppBskyActor.Profile.Profile -> AppBskyActor.Profile.Profile) -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Update the authenticated user's profile by applying a transform function. Reads the current profile, applies the transform, then writes back with optimistic concurrency. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="transform">A function that transforms the current profile record.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" /> (including on swap conflict).</returns>
val p: AppBskyActor.Profile.Profile
val first: ProfileSummary
property List.Head: ProfileSummary with get
val fullProfile: Profile

Type something to start searching.