atproto—crates

A Rust workspace for the AT Protocol · MIT · crates.io 0.14.5 · git 0.15.0-rc.6

AT Protocol,
from the CID up.

Twenty crates that implement the protocol’s primitives directly: content addressing, identity, repositories, authorisation, transport. You can build a service without inheriting somebody’s framework — and two of the twenty are services built that way: a PDS, and an entryway that fronts a cluster of them.

§ 01

What this is

Most of this workspace was extracted from smokesignal.events, an AT Protocol event and RSVP application, and released under MIT so the parts could be used on their own. That origin shows in the shape of the code: these are libraries that were load-bearing in production before they were packaged.

The crates are separable. atproto-dasl will compute CIDs without dragging in a DID resolver; atproto-repo will read a CAR file without an HTTP client. Nothing here is an “SDK” that assumes it owns your main.

Read the version numbers

Fourteen of the twenty crates are published on crates.io at 0.14.5. The workspace in git is ahead of that, at 0.15.0-rc.6, and no release candidate has been published or tagged. If you depend on a version string from the repository README, Cargo will not find it.

Six crates are not on crates.io at all. Two never will be: atproto-pds and atproto-entryway are servers, publish = false, built from the repository or the container images. atproto-firehose, atproto-space and atproto-space-client are publishable and have not been released; use a git dependency. atproto-oauth-dioxus is unmaintained and does not currently build. Read the status column first.

Three more point the other way. atproto-oauth-aip, atproto-xrpcs-helloworld and atproto-oauth-axum are still resolvable on crates.io at 0.14.5, but they have been removed from the workspace and will not be released again. Cargo will happily build against them; nothing upstream will change under them either. See what replaced them.

§ 02

How the crates stack

Layers depend downward. Nothing in a lower band knows the band above it exists, which is why you can take atproto-dasl or atproto-identity in isolation and leave the rest.

Dependency layers of the atproto-crates workspace Six stacked layers, lowest first: data foundation (atproto-dasl); identity (atproto-identity); records and schema (atproto-record, atproto-attestation, atproto-lexicon); repository (atproto-repo, atproto-space); authorisation (atproto-oauth and its unmaintained dioxus integration); and application, clients (atproto-client, atpxrpc, atproto-space-client) and services (atproto-xrpcs, atproto-pds, atproto-entryway). Two side blocks feed the application layer: event ingest (atproto-jetstream, atproto-firehose, atproto-tap) and developer tools (atproto-extras, atpmcp). Application atproto-client · atpxrpc · atproto-space-client atproto-xrpcs · atproto-pds · atproto-entryway Authorisation atproto-oauth + oauth-dioxus (unmaintained) Repository atproto-repo + atproto-space (permissioned realms) Records atproto-record · atproto-attestation atproto-lexicon Identity atproto-identity did:plc · did:web · P-256 · K-256 · Ed25519 Data atproto-dasl CID · DRISL · CAR v1 · block storage Event ingest atproto-jetstream atproto-firehose atproto-tap Tooling atproto-extras atpmcp
Fig. 1Layer order in the workspace. Each band depends only downward. The two side blocks consume the layers beneath them and hand events or utilities up to the application layer.

§ 03

Start here

Take the smallest set that does the job. Every crate is separately versioned and separately publishable; there is no meta-crate that pulls in the workspace.

Cargo.toml — a client that resolves identities and signs recordsListing 1
[dependencies]
atproto-identity    = { version = "0.14.5", features = ["hickory-dns"] }
atproto-record      = "0.14.5"
atproto-attestation = "0.14.5"
atproto-client      = "0.14.5"
reqwest = "0.12"   # the line 0.14.5 is built against
tokio   = { version = "1", features = ["macros", "rt-multi-thread"] }
anyhow  = "1"
Resolve a handle to a DID — compiled and run against 0.14.5Listing 2
use std::time::Duration;

use atproto_identity::resolve::{HickoryDnsResolver, resolve_subject};
use reqwest::{Client, redirect::Policy};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // The handle names the host this fetches from: refuse redirects.
    let http_client = Client::builder()
        .redirect(Policy::none())
        .timeout(Duration::from_secs(10))
        .build()?;
    // Empty slice = system nameservers.
    let dns_resolver = HickoryDnsResolver::create_resolver(&[]);

    let did = resolve_subject(
        &http_client, &dns_resolver, "alice.bsky.social",
    ).await?;
    println!("{did}");
    Ok(())
}

On the git workspace the call is the same, but the client is not: 0.15 is built on reqwest 0.13, and every crate whose API takes a reqwest type re-exports the one it was compiled against. Take Client from atproto_identity::reqwest and drop your own reqwest line; a 0.12 Client is a different type, and the error reads “expected Client, found Client”.

§ 04

Contents

  1. Catalogue — all twenty crates with plate marks, published versions, status, module surface, and links to docs.rs and source.
  2. Guides — six walkthroughs: resolve an identity, verify a repository CAR, sign a record, complete an OAuth flow, write a record to a PDS, consume the firehose.
  3. Concepts — what this workspace means by CID, DRISL, MST, TID, DPoP, NSID, and attestation, with the crate that owns each.
  4. Tools — all eighteen binaries — the command-line tools, the atpmcp MCP server, and the pds and entryway servers — with the feature flags they need and what they print.
  5. PDS — operating atproto-pds: storage, environment, build flags, entryway mode, and an honest account of what is not wired up.
  6. Entryway — operating atproto-entryway in front of one or more PDS instances: keys, member PDSes, configuration, and what it owns that the PDS gives up.