Feeds
Read timelines, author feeds, liked posts, and bookmarks through the Bluesky module.
All examples use taskResult {} -- see the Error Handling guide for details.
Domain Types
FeedItem
A single item in a feed or timeline, pairing a post with the reason it appeared.
Field |
Type |
Description |
|---|---|---|
|
|
The post content and metadata |
|
|
Why this item appeared (repost, pin, or |
FeedReason
Discriminated union indicating why a post appeared in a feed.
Case |
Payload |
Description |
|---|---|---|
|
|
Someone reposted this post |
|
-- |
The post is pinned to the author's profile |
TimelinePost
A flattened, ergonomic view of a post. Engagement counts are plain int64 (not Option), and viewer state is exposed as simple booleans.
Field |
Type |
Description |
|---|---|---|
|
|
The AT-URI of the post record |
|
|
The CID of the post record version |
|
|
The post author |
|
|
The post text content |
|
|
Rich text facets (mentions, links, hashtags) |
|
|
Number of likes |
|
|
Number of reposts |
|
|
Number of replies |
|
|
Number of quote posts |
|
|
When the post was indexed |
|
|
Whether the authenticated user liked this post |
|
|
Whether the authenticated user reposted this post |
|
|
Whether the authenticated user bookmarked this post |
Page<'T>
A paginated result containing a list of items and an optional cursor for the next page.
Field |
Type |
Description |
|---|---|---|
|
|
The items in this page |
|
|
Cursor for the next page, or |
FeedGenerator
Metadata about a custom feed generator.
Field |
Type |
Description |
|---|---|---|
|
|
Feed generator AT-URI |
|
|
Feed generator service DID |
|
|
Feed creator |
|
|
Display name |
|
|
Description |
|
|
Avatar URL |
|
|
Number of likes |
|
|
Whether the generator is currently online |
|
|
Whether the generator is valid |
Functions
Reading Feeds
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Fetch the authenticated user's home timeline |
|
|
|
Fetch posts by a specific user |
|
|
|
Fetch posts that a specific user has liked |
|
|
|
Fetch the authenticated user's bookmarked posts |
|
|
|
Fetch posts from a custom feed generator |
|
|
|
List feed generators created by a user (SRTP) |
|
|
|
Fetch posts from a list-based feed |
|
|
|
Get metadata for a feed generator |
SRTP: getAuthorFeed, getActorLikes, and getActorFeeds accept ProfileSummary, Profile, Handle, or Did for the actor parameter.
taskResult {
let! page = Bluesky.getTimeline agent (Some 25L) None
for item in page.Items do
let author = Handle.value item.Post.Author.Handle
printfn "@%s: %s" author item.Post.Text
}
taskResult {
// Pass a ProfileSummary directly -- no need to extract a handle string
let! page = Bluesky.getAuthorFeed agent someProfile (Some 25L) None
for item in page.Items do
printfn "%s (%d likes)" item.Post.Text item.Post.LikeCount
}
Bookmarks
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Add a post to your bookmarks |
|
|
|
Remove a post from your bookmarks |
SRTP: addBookmark accepts PostRef or TimelinePost. removeBookmark accepts AtUri, PostRef, or TimelinePost.
taskResult {
let! _ = Bluesky.addBookmark agent timelinePost
let! _ = Bluesky.removeBookmark agent timelinePost
return ()
}
Pagination
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Lazily paginate the home timeline |
|
|
|
Lazily paginate an actor's followers |
|
|
|
Lazily paginate notifications |
|
|
|
Paginate blocked users |
|
|
|
Paginate muted users |
|
|
|
Paginate a custom feed |
|
|
|
Paginate a list-based feed |
SRTP: paginateFollowers accepts ProfileSummary, Profile, Handle, or Did for the actor parameter.
Paginators return an IAsyncEnumerable that fetches pages lazily and stops when the server returns no cursor. Iterate with await foreach:
task {
let pages = Bluesky.paginateTimeline agent (Some 50L)
// Consume from F# using TaskSeq or manual IAsyncEnumerator
let enumerator = pages.GetAsyncEnumerator()
let rec loop () = task {
let! hasNext = enumerator.MoveNextAsync()
if hasNext then
match enumerator.Current with
| Ok page ->
for item in page.Items do
printfn "@%s: %s" (Handle.value item.Post.Author.Handle) item.Post.Text
| Error err ->
printfn "Error: %A" err
do! loop ()
}
do! loop ()
do! enumerator.DisposeAsync()
}
For endpoints without a pre-built paginator, use Xrpc.paginate directly. See the Pagination guide for full details.
Matching Feed Reasons
Match on FeedReason to distinguish reposts and pins from organic posts:
for item in page.Items do
match item.Reason with
| Some (FeedReason.Repost (by = reposter)) ->
printfn "Reposted by @%s: %s"
(Handle.value reposter.Handle) item.Post.Text
| Some FeedReason.Pin ->
printfn "[Pinned] %s" item.Post.Text
| None ->
printfn "@%s: %s" (Handle.value item.Post.Author.Handle) item.Post.Text
Custom Feeds
Custom feeds (algorithmic feeds by third parties) are identified by an AT-URI. Use Bluesky.getFeed for convenience, or the raw XRPC wrapper for full control:
taskResult {
let! output =
AppBskyFeed.GetFeed.query
agent
{ Feed = feedUri; Cursor = None; Limit = Some 25L }
for item in output.Feed do
printfn "@%s: %s" (Handle.value item.Post.Author.Handle) item.Post.Text
}
Power Users: Raw XRPC
The raw AppBskyFeed.GetAuthorFeed.query gives access to filter and pin options:
taskResult {
let! output =
AppBskyFeed.GetAuthorFeed.query
agent
{ Actor = "someone.bsky.social"
Cursor = None
Filter = Some AppBskyFeed.GetAuthorFeed.PostsNoReplies
IncludePins = Some true
Limit = Some 25L }
for item in output.Feed do
printfn "%s" item.Post.Text
}
Filter Value |
Description |
|---|---|
|
All posts including replies (default) |
|
Original posts only, no replies |
|
Only posts with images or video |
|
Posts and the author's own reply threads |
|
Only posts with video |
namespace FSharp
--------------------
namespace Microsoft.FSharp
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>
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>
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>
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://<authority>[/<collection>[/<rkey>]]</c>. Maximum length is 8192 characters. </summary>
<remarks> See the AT Protocol specification: https://atproto.com/specs/at-uri-scheme </remarks>
<summary>A paginated result containing a list of items and an optional cursor for the next page.</summary>
module FeedItem from FSharp.ATProto.Bluesky
--------------------
type FeedItem = { Post: TimelinePost Reason: FeedReason option ReplyParent: TimelinePost option }
<summary>A single item in a feed or timeline.</summary>
<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>
<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>
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>
<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>
<summary> Get a specific user's feed (posts by that 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 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>
<summary> Add a post to your bookmarks. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="target">A <see cref="PostRef" /> or <see cref="TimelinePost" /> identifying the post to bookmark.</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Remove a post from your bookmarks. 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 bookmarked post to remove (an <see cref="AtUri" />, <see cref="PostRef" />, or <see cref="TimelinePost" />).</param>
<returns><c>unit</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Paginate the home timeline. Returns an async enumerable of pages. Each element is a <c>Result</c> containing one page of feed items. Pagination stops automatically when the server returns no cursor. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="pageSize">Maximum number of posts per page (optional, pass <c>None</c> for server default).</param>
<returns>An <see cref="System.Collections.Generic.IAsyncEnumerable{T}" /> of paginated results.</returns>
<summary>Gets the element in the collection at the current position of the enumerator.</summary>
<returns>The element in the collection at the current position of the enumerator.</returns>
<summary>Reason a post appeared in a feed.</summary>
<summary> Post text (empty string if not a post record). </summary>