Getting started
atproto_client is transport-agnostic: every function takes a Client,
which is nothing more than a send function you provide. You pick the HTTP
backend (erlang httpc, JS fetch, a test stub); the package never performs
IO on its own.
1. Wire a Client
On the Erlang target, gleam_httpc
works well:
import atproto/xrpc
import atproto_core/xrpc as core_xrpc
import gleam/httpc
import gleam/result
import gleam/string
fn client() -> xrpc.Client {
xrpc.Client(send: fn(req) {
httpc.send_bits(req)
|> result.map_error(fn(error) {
case error {
httpc.ResponseTimeout -> core_xrpc.Timeout
httpc.FailedToConnect(_, _) ->
core_xrpc.ConnectionFailed(string.inspect(error))
_ -> core_xrpc.Other(string.inspect(error))
}
})
})
}
Transport failures are structured (TransportError, defined in
atproto_core/xrpc): your backend maps its own error type onto
InvalidUrl/Timeout/ConnectionFailed/Other, and callers can branch
on them instead of string-matching.
For a browser app, use
atproto_browser instead: it is
the same shapes rebuilt around async fetch and WebCrypto, not a drop-in
swap of the send function.
2. Resolve an identity
atproto/identity resolves a handle or DID to its PDS via a
Slingshot-compatible resolver (identity.default_resolver points at
slingshot.microcosm.blue). Treat the answer as a hint: fine for reading
public data, never for authentication (the OAuth flow re-verifies it
natively):
import atproto/identity
let assert Ok(pds) =
identity.resolve_pds(client(), identity.default_resolver, "someone.bsky.social")
3. Call XRPC
atproto/xrpc has get, post_json, and BitArray variants for blobs. For
repo CRUD, atproto/repo wraps the com.atproto.repo.* endpoints,
including cursor-following list_records and upload_blob:
import atproto/repo
import gleam/dynamic/decode
let assert Ok(rows) =
repo.list_records(
client(),
pds,
access_token,
did,
"app.bsky.feed.post",
decode.dynamic,
)
4. Authenticate
Two options:
atproto/auth: app-password session creation, the simplest way to get an access token for scripts and tooling.atproto/oauth/*: the full atproto OAuth client flow, public or confidential.oauth/flow.startresolves the account natively, verifies its authorization server, and pushes the request (PAR + PKCE + DPoP);oauth/flow.callbacktakes the whole callback query, checksstateandiss, exchanges the code, and returns aVerifiedSessionwhosesubis bound to the account.oauth/tokensrefreshes and revokes it;oauth/resourcesigns requests to the session’s PDS and nothing else. Start at theatproto/oauth/flowmodule docs.
For OAuth, your Client is part of the trust boundary: leave redirects off
(httpc.follow_redirects defaults to False), set a timeout, and on a
server refuse connections that resolve to private, loopback, or link-local
addresses. The library already rejects non-https URLs, IP literals, and
local names before anything is sent.
Going further
- Generated typed clients:
atproto_codegenemits one typed function per lexicon query/procedure on top ofatproto/xrpc. - Backlinks:
atproto/constellationqueries the microcosm backlink index. - Every module carries doc comments with the finer details; this guide is only the map.