app.rocksky.* records straight to the user’s PDS (scrobbles,
likes, follows, shouts, now-playing). They are not thin HTTP wrappers.
Two implementations share one behaviour:
- Shared Rust core — Python, Ruby, Kotlin, Clojure, and the BEAM trio
(Erlang/Elixir/Gleam) are thin bindings over the same Rust engine
(
rocksky-sdk), so auth, record writing, dedup, and the identity hashes are identical everywhere. - Native ecosystem — Go is built on indigo,
TypeScript on atcute, and Rust is the
rocksky-sdkcrate itself (on jacquard).
TypeScript
@rocksky/sdk — built on atcutePython
rocksky — native core bindingsRust
rocksky-sdk crate — jacquardGo
sdk/go/rocksky — built on indigoRuby
rocksky gem — native core (fiddle)Kotlin
app.rocksky:rocksky-kotlin — UniFFIElixir
rocksky_ex — over the rocksky_erl NIFErlang
rocksky_erl — Rustler NIFClojure
app.rocksky/sdk — Panama FFMGleam
rocksky — over the rocksky_erl NIFCommon surface
Every SDK exposes the same three things:- Reads — a client over the public AppView (
https://api.rocksky.app, overridable) covering the fullapp.rocksky.*read catalog:globalStats,topTracks,topArtists,profile,scrobbles,songs,albums,artists,search, catalog/loved/follow queries, plus a universalget(nsid, params)escape hatch that calls any read query by nsid. Charts take a typedDateInterval(all-time, last N days/weeks/months/years, or a custom range), andmatchSongresolves a bare title + artist into full canonical metadata. Each read client also accepts an optional bearer access token for auth-gated queries. - Writes — an Agent that logs in with an app password, resolves the
account’s PDS, and writes records:
scrobble,scrobble_match(match-then-write from a title/artist input object, with optional album override,mbId/isrcmatch anchors, and a scrobbled-at timestamp),createSong/createAlbum/createArtist,like,follow,shout, now-playing. - Identity hashes —
songHash/albumHash/artistHash(lowercase-hex SHA-256), byte-for-byte identical across every SDK and the server.
Duplicate prevention & real-time sync
The Rust, Go, and TypeScript SDKs (and the native-core bindings) ship an optional local dedup index keyed by the identity hashes:syncRepo() backfills it
from the repo CAR (com.atproto.sync.getRepo), and hydrateFromJetstream()
keeps it live off the Bluesky Jetstream firehose. With an index attached, write
verbs skip records that already exist and never duplicate a same-second scrobble.