Chat / Direct Messages
Send and receive Bluesky direct messages through the Chat module.
All examples use taskResult {} -- see the Error Handling guide for details. The chat proxy header (atproto-proxy: did:web:api.bsky.chat#bsky_chat) is applied automatically -- you use the same agent as for everything else.
Domain Types
ConvoSummary
A summary of a chat conversation.
Field |
Type |
Description |
|---|---|---|
|
|
The conversation identifier |
|
|
Participants in the conversation |
|
|
The most recent message summary, if any |
|
|
Number of unread messages |
|
|
Whether notifications are muted for this conversation |
LastMessage
Summary of the most recent message in a conversation.
Field |
Type |
Description |
|---|---|---|
|
|
Message text |
|
|
DID of the sender |
|
|
When the message was sent |
ChatMessage
Discriminated union representing a message in a conversation. Uses [<RequireQualifiedAccess>].
Case |
Fields |
Description |
|---|---|---|
|
|
A visible message |
|
|
A deleted message placeholder |
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 |
Functions
SRTP: All functions that take a convoId parameter accept either a ConvoSummary or a string conversation ID.
Conversations
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
List conversations, most recent first |
|
|
|
Get or create a conversation with the given members |
|
|
|
Get a conversation by ID |
|
|
|
Accept a conversation request |
|
|
|
Leave a conversation |
taskResult {
let! profile = Bluesky.getProfile agent aliceHandle
let! convo = Chat.getConvoForMembers agent [ profile.Did ]
printfn "Conversation: %s (members: %d)" convo.Id convo.Members.Length
}
Messages
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Send a message with auto-detected rich text |
|
|
|
Get messages, most recent first |
|
|
|
Delete a message (for you only) |
sendMessage automatically detects links, mentions, and hashtags -- just like Bluesky.post. No extra steps needed.
taskResult {
let! msg = Chat.sendMessage agent convo "Check out https://atproto.com!"
match msg with
| ChatMessage.Message m -> printfn "Sent: %s (id: %s)" m.Text m.Id
| ChatMessage.Deleted _ -> ()
}
taskResult {
let! page = Chat.getMessages agent convo (Some 20L) None
for m in page.Items do
match m with
| ChatMessage.Message msg ->
printfn "[%s] %s" (Did.value msg.Sender) msg.Text
| ChatMessage.Deleted del ->
printfn "(deleted: %s)" del.Id
}
Read State
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Mark a conversation as read |
|
|
|
Mark all conversations as read; returns count updated |
taskResult {
let! _ = Chat.markRead agent convo
let! updatedCount = Chat.markAllRead agent
printfn "Marked %d conversations as read" updatedCount
}
Muting
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Mute a conversation (no notifications) |
|
|
|
Unmute a conversation |
taskResult {
let! _ = Chat.muteConvo agent convo
let! _ = Chat.unmuteConvo agent convo
return ()
}
Reactions
Function |
Accepts |
Returns |
Description |
|---|---|---|---|
|
|
|
Add an emoji reaction to a message |
|
|
|
Remove an emoji reaction from a message |
taskResult {
let! _ = Chat.addReaction agent convo msgId "❤️"
let! _ = Chat.removeReaction agent convo msgId "❤️"
return ()
}
Power Users: Raw XRPC
For full control over facets or to include an embed (e.g., sharing a post into a DM), drop to the raw XRPC wrapper. You must apply the chat proxy header manually when using raw wrappers:
task {
let text = "Check out https://atproto.com!"
let! facets = RichText.parse agent text
let! result =
ChatBskyConvo.SendMessage.call (AtpAgent.withChatProxy agent)
{ ConvoId = convo.Id
Message =
{ Text = text
Facets = if facets.IsEmpty then None else Some facets
Embed = None } }
match result with
| Ok msg -> printfn "Sent with custom facets"
| Error err -> printfn "Failed: %A" err
}
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 ConvoSummary from FSharp.ATProto.Bluesky
--------------------
type ConvoSummary = { Id: string Members: ProfileSummary list LastMessage: LastMessage option UnreadCount: int64 IsMuted: bool }
<summary>A summary of a chat conversation.</summary>
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> 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 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>
<summary> Convenience methods for Bluesky direct message (DM) and chat operations. Wraps the <c>chat.bsky.convo.*</c> XRPC endpoints with a simplified API. All methods require an authenticated <see cref="AtpAgent" />. The chat proxy header (<c>atproto-proxy: did:web:api.bsky.chat#bsky_chat</c>) is applied automatically -- callers do not need to use <see cref="AtpAgent.withChatProxy" /> manually. </summary>
<summary> Get an existing conversation with the specified members, or create a new one if none exists. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="members">A list of DIDs of the conversation members (excluding the authenticated user, who is added automatically).</param>
<returns>A <see cref="ConvoSummary" />, or an <see cref="XrpcError" />.</returns>
<summary> Send a message to a conversation. Rich text (links, mentions, hashtags) is automatically detected and resolved, matching the behaviour of <c>Bluesky.post</c>. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<param name="text">The message text content. Links, mentions, and hashtags are auto-detected.</param>
<returns>The sent message as a <see cref="ChatMessage" />, or an <see cref="XrpcError" />.</returns>
module ChatMessage from FSharp.ATProto.Bluesky
--------------------
type ChatMessage = | Message of {| Embed: PostEmbed option; Id: string; Sender: Did; SentAt: DateTimeOffset; Text: string |} | Deleted of {| Id: string; Sender: Did |}
<summary>A message in a chat conversation.</summary>
<summary> Get messages in a conversation, ordered by most recent first. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<param name="limit">Maximum number of messages to return. Pass <c>None</c> for the server default.</param>
<param name="cursor">Pagination cursor from a previous response. Pass <c>None</c> for the most recent messages.</param>
<returns>A page of <see cref="ChatMessage" /> with an optional cursor, or an <see cref="XrpcError" />.</returns>
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>
<summary> Extract the string representation of a DID. </summary>
<param name="did">The DID to extract the value from.</param>
<returns>The full DID string (e.g. <c>"did:plc:z72i7hdynmk6r22z27h6tvur"</c>).</returns>
<summary> Mark a conversation as read up to the latest message. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Mark all conversations as read. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<returns>The number of conversations updated, or an <see cref="XrpcError" />.</returns>
<summary> Mute a conversation. Muted conversations do not generate notifications. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Unmute a previously muted conversation, restoring notifications. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Add an emoji reaction to a message in a conversation. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<param name="messageId">The ID of the message to react to.</param>
<param name="emoji">The emoji reaction value (e.g., a Unicode emoji string).</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Remove an emoji reaction from a message in a conversation. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="convoId">A <see cref="ConvoSummary" /> or <c>string</c> conversation ID.</param>
<param name="messageId">The ID of the message to remove the reaction from.</param>
<param name="emoji">The emoji reaction value to remove.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Rich text processing for Bluesky posts. Detects mentions, links, and hashtags in text and resolves them to facets with correct UTF-8 byte offsets as required by the AT Protocol. </summary>
<summary> Detect and resolve all rich text facets in a single step. Combines <see cref="detect" /> and <see cref="resolve" />: scans the text for mentions, links, and hashtags, then resolves mentions to DIDs. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="text">The text to scan and resolve.</param>
<returns>A list of resolved <see cref="AppBskyRichtext.Facet.Facet" /> records.</returns>
<example><code> let! facets = RichText.parse agent "Hello @my-handle.bsky.social! Check https://example.com #atproto" </code></example>
<summary> Returns a copy of the agent configured to proxy requests through the Bluesky Chat service. Adds the <c>atproto-proxy: did:web:api.bsky.chat#bsky_chat</c> header. </summary>
<param name="agent">The agent to copy with chat proxy configuration.</param>
<returns>A new <see cref="AtpAgent" /> with the chat proxy header prepended to <see cref="AtpAgent.ExtraHeaders" />.</returns>
<remarks> The returned agent shares the same <see cref="System.Net.Http.HttpClient" /> as the original but has an independent <see cref="AtpAgent.Session" /> field (it is a record copy). If you need chat functionality, prefer using the <see cref="FSharp.ATProto.Bluesky.Chat" /> module functions directly — they handle the proxy header automatically and always use the current session from the original agent. </remarks>