Account
FSharp.ATProto provides functions for authentication, session management, account lifecycle, and agent configuration. These are the entry points for all interaction with the AT Protocol.
All examples use taskResult {}. See the Error Handling guide for details.
open FSharp.ATProto.Core
open FSharp.ATProto.Bluesky
open FSharp.ATProto.Syntax
Authentication
Function |
Returns |
Description |
|---|---|---|
|
|
Authenticate with base URL, identifier, and app password |
|
|
Login with a custom |
|
|
Resume from saved session data (no network call) |
|
|
Resume with a custom |
|
|
Terminate the session on the server |
Basic Login
taskResult {
let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"
printfn "Logged in as %s" (Did.value agent.Session.Value.Did)
}
Session Persistence
The AtpSession record contains the JWTs and identity needed to restore a session without re-authenticating:
type AtpSession =
{ AccessJwt : string
RefreshJwt : string
Did : Did
Handle : Handle }
Save the session after login, then restore it later:
// Save after login
taskResult {
let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"
let session = agent.Session.Value
// Persist session.AccessJwt, session.RefreshJwt, session.Did, session.Handle
return agent
}
// Restore later (no network call)
let restoredAgent = Bluesky.resumeSession "https://bsky.social" savedSession
The access JWT expires after a short period. The library automatically refreshes it using the refresh JWT on the first 401 response, so a restored session remains functional as long as the refresh JWT is valid.
Custom HttpClient
Use loginWithClient or resumeSessionWithClient when you need a custom HTTP handler (e.g., for testing with a mock handler or custom timeouts):
let client = new System.Net.Http.HttpClient()
client.Timeout <- System.TimeSpan.FromSeconds(30.0)
taskResult {
let! agent = Bluesky.loginWithClient client "https://bsky.social" "handle.bsky.social" "app-password"
return agent
}
Account Lifecycle
Function |
Description |
|---|---|
|
Create a new account on a PDS |
|
Request deletion (sends confirmation email) |
|
Confirm deletion with token from email |
Creating an Account
taskResult {
let handle = Handle.parse "new-user.my-pds.example" |> Result.defaultWith failwith
let! agent =
Bluesky.createAccount
"https://my-pds.example"
handle
(Some "user@example.com")
(Some "strong-password")
None // invite code, if required by the PDS
printfn "Account created: %s" (Did.value agent.Session.Value.Did)
}
Deleting an Account
Account deletion is a two-step process. First, request deletion (which sends a confirmation email), then confirm with the token from the email:
taskResult {
// Step 1: Request deletion -- sends confirmation email
do! Bluesky.requestAccountDelete agent
// Step 2: After receiving the email, confirm with the token
do! Bluesky.deleteAccount agent "account-password" "token-from-email"
// Agent session is cleared after successful deletion
}
Agent Configuration
Labeler Subscriptions
AtpAgent.configureLabelers returns a new agent configured with the atproto-accept-labelers header. This tells the server which labeler services to include labels from in responses:
taskResult {
let! agent = Bluesky.login "https://bsky.social" "handle.bsky.social" "app-password"
// Subscribe to a custom labeler (redact=false means labels are informational)
let agent = AtpAgent.configureLabelers [ "did:plc:my-labeler", false ] agent
// Subscribe with redact=true (labeler's labels can hide content entirely)
let agent = AtpAgent.configureLabelers [ "did:plc:my-labeler", true ] agent
// Multiple labelers
let agent =
AtpAgent.configureLabelers
[ "did:plc:labeler-one", false
"did:plc:labeler-two", true ]
agent
return agent
}
Chat Proxy
AtpAgent.withChatProxy returns a new agent with the proxy header needed for Bluesky DM operations:
let chatAgent = AtpAgent.withChatProxy agent
The chat proxy header (atproto-proxy: did:web:api.bsky.chat#bsky_chat) is required for all chat.bsky.* endpoints. See the Chat guide for usage.
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>
<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>
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> The current authenticated session, or <c>None</c> if not logged in. This field is mutable and is updated automatically by <see cref="AtpAgent.login" /> and by the automatic token refresh logic in <see cref="Xrpc" />. </summary>
<summary>The DID (Decentralized Identifier) of the authenticated user (e.g. "did:plc:xyz123").</summary>
val string: value: 'T -> string
--------------------
type string = System.String
<summary> An authenticated session with an AT Protocol Personal Data Server (PDS). Obtained by calling <see cref="AtpAgent.login" /> with valid credentials. </summary>
<remarks> Sessions contain a short-lived access JWT for API calls and a longer-lived refresh JWT for obtaining new access tokens. The <see cref="Xrpc" /> module automatically refreshes expired access tokens using the refresh JWT. </remarks>
<summary> Construct an agent from saved session data without making any network calls. Use this to restore a session from persisted tokens. </summary>
<param name="baseUrl">The PDS base URL (e.g. <c>"https://bsky.social"</c>).</param>
<param name="session">A previously obtained <see cref="AtpSession" />.</param>
<returns>An authenticated <see cref="AtpAgent" /> with the given session.</returns>
type HttpClient = inherit HttpMessageInvoker new: unit -> unit + 2 overloads member CancelPendingRequests: unit -> unit member DeleteAsync: requestUri: string -> Task<HttpResponseMessage> + 3 overloads member GetAsync: requestUri: string -> Task<HttpResponseMessage> + 7 overloads member GetByteArrayAsync: requestUri: string -> Task<byte array> + 3 overloads member GetStreamAsync: requestUri: string -> Task<Stream> + 3 overloads member GetStringAsync: requestUri: string -> Task<string> + 3 overloads member PatchAsync: requestUri: string * content: HttpContent -> Task<HttpResponseMessage> + 3 overloads member PostAsync: requestUri: string * content: HttpContent -> Task<HttpResponseMessage> + 3 overloads ...
<summary>Provides a class for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.</summary>
--------------------
System.Net.Http.HttpClient() : System.Net.Http.HttpClient
System.Net.Http.HttpClient(handler: System.Net.Http.HttpMessageHandler) : System.Net.Http.HttpClient
System.Net.Http.HttpClient(handler: System.Net.Http.HttpMessageHandler, disposeHandler: bool) : System.Net.Http.HttpClient
<summary>Gets or sets the timespan to wait before the request times out.</summary>
<exception cref="T:System.ArgumentOutOfRangeException">The timeout specified is less than or equal to zero and is not <see cref="F:System.Threading.Timeout.InfiniteTimeSpan" /> -or- The timeout specified is greater than <see cref="F:System.Int32.MaxValue" /> milliseconds.</exception>
<exception cref="T:System.InvalidOperationException">An operation has already been started on the current instance.</exception>
<exception cref="T:System.ObjectDisposedException">The current instance has been disposed.</exception>
<returns>The timespan to wait before the request times out.</returns>
[<Struct>] type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 4 overloads member Add: ts: TimeSpan -> TimeSpan member CompareTo: value: obj -> int + 1 overload member Divide: divisor: float -> TimeSpan + 1 overload member Duration: unit -> TimeSpan member Equals: value: obj -> bool + 2 overloads member GetHashCode: unit -> int member Multiply: factor: float -> TimeSpan member Negate: unit -> TimeSpan member Subtract: ts: TimeSpan -> TimeSpan ...
<summary>Represents a time interval.</summary>
--------------------
System.TimeSpan ()
System.TimeSpan(ticks: int64) : System.TimeSpan
System.TimeSpan(hours: int, minutes: int, seconds: int) : System.TimeSpan
System.TimeSpan(days: int, hours: int, minutes: int, seconds: int) : System.TimeSpan
System.TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int) : System.TimeSpan
System.TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int) : System.TimeSpan
System.TimeSpan.FromSeconds(value: float) : System.TimeSpan
System.TimeSpan.FromSeconds(seconds: int64, ?milliseconds: int64, ?microseconds: int64) : System.TimeSpan
<summary> Create an agent with a custom <see cref="System.Net.Http.HttpClient" /> and authenticate. Useful for testing with mock HTTP handlers or custom client configuration. </summary>
<param name="client">The HTTP client to use for all requests.</param>
<param name="baseUrl">The PDS base URL (e.g. <c>"https://bsky.social"</c>).</param>
<param name="identifier">A handle (e.g. <c>"my-handle.bsky.social"</c>) or DID.</param>
<param name="password">An app password (not the account password).</param>
<returns>An authenticated <see cref="AtpAgent" /> on success, 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> Parse and validate a handle string. </summary>
<param name="s"> A handle string in domain-name format (e.g. <c>"my-handle.bsky.social"</c>). Must be a valid hostname with at least two segments, each segment starting and ending with an alphanumeric character, and the TLD starting with a letter. </param>
<returns><c>Ok</c> with a validated <see cref="Handle" />, or <c>Error</c> with a message describing the validation failure. Validation failures include: null input, exceeding the 253-character limit, or invalid hostname syntax. </returns>
<example><code> match Handle.parse "my-handle.bsky.social" with | Ok handle -> printfn "Valid: %s" (Handle.value handle) | Error e -> printfn "Invalid: %s" e </code></example>
module Result from Microsoft.FSharp.Core
--------------------
[<Struct>] type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
<summary> Create a new account on the given PDS and return an authenticated agent. Only the handle is required; email, password, and invite code are optional. </summary>
<param name="baseUrl">The PDS base URL (e.g. <c>"https://bsky.social"</c>).</param>
<param name="handle">The requested handle for the new account.</param>
<param name="email">Optional email address for the account.</param>
<param name="password">Optional password. May need to meet instance-specific strength requirements.</param>
<param name="inviteCode">Optional invite code, if the PDS requires one.</param>
<returns>An authenticated <see cref="AtpAgent" /> on success, or an <see cref="XrpcError" />.</returns>
<summary> Request account deletion. Sends a confirmation email to the account's email address. After receiving the email, call <see cref="deleteAccount" /> with the token from the email. Requires an authenticated session. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Delete the authenticated user's account. Requires a token from <see cref="requestAccountDelete" /> (sent via email) and the account password. After successful deletion, the agent's session is cleared. </summary>
<param name="agent">An authenticated <see cref="AtpAgent" />.</param>
<param name="password">The account password.</param>
<param name="token">The deletion token received via email after calling <see cref="requestAccountDelete" />.</param>
<returns><c>Ok ()</c> on success, or an <see cref="XrpcError" />.</returns>
<summary> Returns a copy of the agent configured with the <c>atproto-accept-labelers</c> header. This tells the server which labeler services to include labels from in responses. </summary>
<param name="labelers"> A list of labeler DIDs and optional redact flags. Each entry is a tuple of (labeler DID string, redact flag). When <c>redact=true</c>, the labeler's labels can cause content to be entirely removed from responses. </param>
<param name="agent">The agent to copy with labeler configuration.</param>
<returns>A new <see cref="AtpAgent" /> with the <c>atproto-accept-labelers</c> header.</returns>
<remarks> The header format follows IETF RFC-8941 Structured Field Values: <c>did:plc:abc123;redact, did:plc:def456</c></remarks>
<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>