Header menu logo FSharp.ATProto

Lists

FSharp.ATProto provides convenience functions for creating and managing Bluesky lists (moderation, curation, and reference lists) and starter packs.

All examples use taskResult {}. See the Error Handling guide for details.

open FSharp.ATProto.Core
open FSharp.ATProto.Bluesky
open FSharp.ATProto.Syntax

Domain Types

Type

Fields

Description

ListRef

Uri : AtUri

Reference to a list record

ListItemRef

Uri : AtUri

Reference to a list item record

StarterPackRef

Uri : AtUri

Reference to a starter pack record

ListView

Uri, Name, Purpose, Description, Avatar, Creator : ProfileSummary, ListItemCount, IsMuted, IsBlocked

Summary of a list

ListDetail

List : ListView, Items : ProfileSummary list, Cursor : string option

A list with its member profiles

List Purpose

The AppBskyGraph.Defs.ListPurpose DU controls what kind of list you are creating:

Case

Description

Modlist

Moderation list -- used to mute or block all members

Curatelist

Curation list -- used for custom feeds and starter packs

Referencelist

General-purpose reference list

Reading Lists

Function

Description

Bluesky.getList

Get list details and members

Bluesky.getLists

Get lists created by a user (SRTP: Handle, Did, ProfileSummary, Profile)

Bluesky.getListFeed

Get posts from a list-based feed

taskResult {
    let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"

    // Get lists created by someone
    let! page = Bluesky.getLists agent someHandle None None

    for list in page.Items do
        printfn "%s (%d members)" list.Name list.ListItemCount

    // Get a specific list with its members
    let! detail = Bluesky.getList agent listUri None None

    for m in detail.Items do
        printfn "  - %s" (Handle.value m.Handle)
}

Managing Lists

Function

Description

Bluesky.createList

Create a new list (name, purpose, description)

Bluesky.deleteList

Delete a list

Bluesky.addListItem

Add a user to a list

Bluesky.removeListItem

Remove a user from a list

Creating a List and Adding Members

taskResult {
    let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"

    // Create a curation list
    let! listRef =
        Bluesky.createList agent
            "F# Developers"
            AppBskyGraph.Defs.ListPurpose.Curatelist
            (Some "People building cool things with F#")

    // Add members to the list
    let! item1 = Bluesky.addListItem agent listRef.Uri user1Did
    let! item2 = Bluesky.addListItem agent listRef.Uri user2Did

    // Remove a member later
    do! Bluesky.removeListItem agent item1.Uri

    // Delete the entire list
    do! Bluesky.deleteList agent listRef.Uri
}

List Feed

For curation lists, you can read posts from all list members as a feed:

taskResult {
    let! page = Bluesky.getListFeed agent listUri (Some 25L) None

    for item in page.Items do
        printfn "%s: %s" (Handle.value item.Post.Author.Handle) item.Post.Text
}

Starter Packs

Starter packs are built on top of curation lists. Create a list first, then wrap it in a starter pack:

Function

Description

Bluesky.createStarterPack

Create a starter pack from a list

Bluesky.deleteStarterPack

Delete a starter pack

taskResult {
    let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"

    // Create the underlying list first
    let! listRef =
        Bluesky.createList agent
            "Welcome to F# on Bluesky"
            AppBskyGraph.Defs.ListPurpose.Curatelist
            (Some "Great accounts to follow if you're into F#")

    // Add members to the list
    let! _ = Bluesky.addListItem agent listRef.Uri user1Did
    let! _ = Bluesky.addListItem agent listRef.Uri user2Did

    // Create the starter pack wrapping the list
    let! starterPackRef =
        Bluesky.createStarterPack agent
            "F# Starter Pack"
            listRef.Uri
            (Some "Follow these accounts to get started with F# on Bluesky")
            None // optional feed URIs

    printfn "Starter pack created: %s" (AtUri.value starterPackRef.Uri)
}
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 someHandle: 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 listUri: 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 user1Did: Did
Multiple items
module Did from FSharp.ATProto.Syntax
<summary> Functions for creating, validating, and extracting data from <see cref="Did" /> values. </summary>

--------------------
type Did = private | Did of string override ToString: unit -> string
<summary> A decentralized identifier (DID) as defined by the AT Protocol. DIDs are the primary stable identifier for accounts. Two methods are currently supported: <c>did:plc:</c> (hosted, managed by PLC directory) and <c>did:web:</c> (self-hosted, DNS-based). </summary>
<remarks> See the AT Protocol specification: https://atproto.com/specs/did and the W3C DID specification: https://www.w3.org/TR/did-core/ </remarks>
val user2Did: Did
val taskResult: TaskResultBuilder
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 login: baseUrl: string -> identifier: string -> password: string -> System.Threading.Tasks.Task<Result<AtpAgent,XrpcError>>
val page: Page<ListView>
val getLists: agent: AtpAgent -> actor: 'a -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<Page<ListView>,XrpcError>> (requires member ToActorString)
<summary> Get lists created by 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 lists to return (optional).</param>
<param name="cursor">Pagination cursor from a previous response (optional).</param>
<returns>A page of <see cref="ListView" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
union case Option.None: Option<'T>
Multiple items
val list: ListView

--------------------
type 'T list = List<'T>
Page.Items: ListView list
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
ListView.Name: string
ListView.ListItemCount: int64
val detail: ListDetail
val getList: agent: AtpAgent -> listUri: AtUri -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<ListDetail,XrpcError>>
<summary> Get a list's details and members. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listUri">The AT-URI of the list.</param>
<param name="limit">Maximum number of list items to return (optional).</param>
<param name="cursor">Pagination cursor from a previous response (optional).</param>
<returns>A <see cref="ListDetail" /> containing list metadata and member profiles, or an <see cref="XrpcError" />.</returns>
val m: ProfileSummary
ListDetail.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 listRef: ListRef
val createList: agent: AtpAgent -> name: string -> purpose: AppBskyGraph.Defs.ListPurpose -> description: string option -> System.Threading.Tasks.Task<Result<ListRef,XrpcError>>
<summary> Create a new list. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="name">The name of the list.</param>
<param name="purpose">The list purpose (curate list, mod list, or reference list).</param>
<param name="description">Optional description for the list.</param>
<returns>A <see cref="ListRef" /> on success, or an <see cref="XrpcError" />.</returns>
module AppBskyGraph from FSharp.ATProto.Bluesky
module Defs from FSharp.ATProto.Bluesky.AppBskyGraph
type ListPurpose = | Modlist | Curatelist | Referencelist | Unknown of string
union case AppBskyGraph.Defs.ListPurpose.Curatelist: AppBskyGraph.Defs.ListPurpose
union case Option.Some: Value: 'T -> Option<'T>
val item1: ListItemRef
val addListItem: agent: AtpAgent -> listUri: AtUri -> subject: Did -> System.Threading.Tasks.Task<Result<ListItemRef,XrpcError>>
<summary> Add an account to a list. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listUri">The AT-URI of the list.</param>
<param name="subject">The DID of the account to add.</param>
<returns>A <see cref="ListItemRef" /> on success, or an <see cref="XrpcError" />.</returns>
ListRef.Uri: AtUri
<summary>The AT-URI of the list record.</summary>
val item2: ListItemRef
val removeListItem: agent: AtpAgent -> listItemUri: AtUri -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Remove an account from a list by deleting the list item record. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listItemUri">The AT-URI of the list item record to remove.</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
ListItemRef.Uri: AtUri
<summary>The AT-URI of the list item record.</summary>
val deleteList: agent: AtpAgent -> listUri: AtUri -> System.Threading.Tasks.Task<Result<unit,XrpcError>>
<summary> Delete a list. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listUri">The AT-URI of the list to delete.</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
val page: Page<FeedItem>
val getListFeed: agent: AtpAgent -> listUri: AtUri -> limit: int64 option -> cursor: string option -> System.Threading.Tasks.Task<Result<Page<FeedItem>,XrpcError>>
<summary> Get posts from a list feed. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="listUri">The AT-URI of the list.</param>
<param name="limit">Maximum number of posts to return (optional).</param>
<param name="cursor">Pagination cursor from a previous response (optional).</param>
<returns>A page of <see cref="FeedItem" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
val item: FeedItem
Page.Items: FeedItem list
FeedItem.Post: TimelinePost
TimelinePost.Author: ProfileSummary
TimelinePost.Text: string
val starterPackRef: StarterPackRef
val createStarterPack: agent: AtpAgent -> name: string -> listUri: AtUri -> description: string option -> feedUris: AtUri list option -> System.Threading.Tasks.Task<Result<StarterPackRef,XrpcError>>
<summary> Create a starter pack. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="name">The name of the starter pack.</param>
<param name="listUri">The AT-URI of the list containing the starter pack members.</param>
<param name="description">Optional description for the starter pack.</param>
<param name="feedUris">Optional list of feed generator URIs to include.</param>
<returns>A <see cref="StarterPackRef" /> on success, or an <see cref="XrpcError" />.</returns>
val value: AtUri -> string
<summary> Extract the string representation of an AT-URI. </summary>
<param name="atUri">The AT-URI to extract the value from.</param>
<returns>The full AT-URI string (e.g. <c>"at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3k2la3b"</c>).</returns>
StarterPackRef.Uri: AtUri
<summary>The AT-URI of the starter pack record.</summary>

Type something to start searching.