A decentralized music tracking and discovery platform built on AT Protocol 🎵
rocksky.app
spotify
atproto
lastfm
musicbrainz
scrobbling
listenbrainz
1---
2title: "Ruby"
3description: "Idiomatic Ruby client for the Rocksky XRPC API."
4icon: "gem"
5---
6
7`rocksky` is a stdlib-only Ruby client for the Rocksky API.
8
9```ruby
10client = Rocksky.new(token: ENV["ROCKSKY_TOKEN"])
11
12client.actor.get_profile(did: "tsiry-sandratraina.com")
13client.charts.get_top_artists(limit: 10, start_date: "2025-01-01")
14client.scrobble.create_scrobble(title: "In Bloom", artist: "Nirvana")
15```
16
17Every XRPC NSID maps to a method on a resource object.
18`app.rocksky.actor.getProfile` becomes `client.actor.get_profile(...)`.
19`app.rocksky.scrobble.createScrobble` becomes
20`client.scrobble.create_scrobble(...)`. Kwargs in, parsed JSON out.
21
22## Install
23
24```ruby
25# Gemfile
26gem "rocksky"
27```
28
29```bash
30gem install rocksky
31```
32
33Requires Ruby 3.0+. Depends only on Ruby's stdlib (`net/http`, `json`, `uri`).
34
35## Quick start
36
37```ruby
38require "rocksky"
39
40# Reads ROCKSKY_BASE_URL and ROCKSKY_TOKEN from the env when omitted.
41client = Rocksky.new
42
43profile = client.actor.get_profile(did: "tsiry-sandratraina.com")
44puts profile["displayName"]
45```
46
47For authenticated calls, pass a bearer token:
48
49```ruby
50client = Rocksky.new(token: "eyJ...")
51client.scrobble.create_scrobble(title: "In Bloom", artist: "Nirvana")
52```
53
54`with_token` derives a new client without mutating the original — handy when a
55single base client is shared across users in a web app:
56
57```ruby
58base = Rocksky.new
59def for_user(base, token) = base.with_token(token)
60```
61
62## Resources
63
64| Resource | Methods |
65| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
66| `client.actor` | `get_profile`, `get_actor_albums/artists/songs/scrobbles/playlists/loved_songs`, `get_actor_neighbours`, `get_actor_compatibility` |
67| `client.album` | `get_album`, `get_albums`, `get_album_tracks` |
68| `client.apikey` | `get_apikeys`, `create_apikey`, `update_apikey`, `remove_apikey` *(auth)* |
69| `client.artist` | `get_artist`, `get_artists`, `get_artist_albums/tracks/listeners/recent_listeners` |
70| `client.charts` | `get_scrobbles_chart`, `get_top_artists`, `get_top_tracks` |
71| `client.feed` | `search`, `get_feed_generators/generator/feed`, `get_stories`, `get_recommendations`, `get_artist_recommendations`, `get_album_recommendations` |
72| `client.graph` | `follow_account`, `unfollow_account`, `get_followers`, `get_follows`, `get_known_followers` *(auth)* |
73| `client.like` | `like_song`, `dislike_song`, `like_shout`, `dislike_shout` *(auth)* |
74| `client.mirror` | `get_mirror_sources`, `put_mirror_source` *(auth)* |
75| `client.player` | `play`, `pause`, `next`, `previous`, `seek`, `play_file`, `play_directory`, `add_items_to_queue`, `get_currently_playing`, `get_playback_queue` |
76| `client.playlist` | `get_playlist`, `get_playlists`, `create_playlist`, `remove_playlist`, `start_playlist`, `insert_files`, `insert_directory` |
77| `client.scrobble` | `create_scrobble`, `get_scrobble`, `get_scrobbles` |
78| `client.shout` | `create_shout`, `reply_shout`, `remove_shout`, `report_shout`, `get_*_shouts`, `get_shout_replies` |
79| `client.song` | `get_song`, `get_songs`, `get_song_recent_listeners`, `match_song`, `create_song` |
80| `client.spotify` | `play`, `pause`, `next`, `previous`, `seek`, `get_currently_playing` *(auth)* |
81| `client.stats` | `get_stats`, `get_wrapped` |
82
83For anything not covered:
84
85```ruby
86client.query("app.rocksky.actor.getProfile", did: "did:plc:7vdlgi2bflelz7mmuxoqjfcr")
87client.procedure("app.rocksky.like.likeSong", body: { uri: "at://..." })
88```
89
90## Conventions
91
92- **Keyword args** for every parameter. Ruby `snake_case` maps to the
93 lexicon's `camelCase` (`start_date:` → `startDate`).
94- **`nil` is dropped.** Pass `nil` for any optional param and it won't be sent.
95- **Arrays are CSV-joined.** Lexicon list params accept Ruby arrays.
96- **Hashes in, hashes out.** Responses come back as plain `Hash` (string
97 keys), mirroring the lexicon JSON 1:1.
98
99## Errors
100
101```ruby
102begin
103 client.song.get_song(uri: "at://does-not-exist")
104rescue Rocksky::NotFound => e
105 puts "missing: #{e.message} (status=#{e.status}, nsid=#{e.nsid})"
106rescue Rocksky::RateLimited
107 sleep 5; retry
108rescue Rocksky::Error => e
109 warn "rocksky failure: #{e.class}: #{e.message}"
110end
111```
112
113| Class | Status |
114| ------------------------- | ------------------- |
115| `Rocksky::BadRequest` | 400 |
116| `Rocksky::Unauthorized` | 401 |
117| `Rocksky::Forbidden` | 403 |
118| `Rocksky::NotFound` | 404 |
119| `Rocksky::RateLimited` | 429 |
120| `Rocksky::ServerError` | 5xx |
121| `Rocksky::HTTPError` | any other non-2xx |
122| `Rocksky::TransportError` | DNS/TCP/timeouts |
123
124## Configuration
125
126| Option | Default | Env var |
127| -------------- | ----------------------------- | --------------------- |
128| `base_url` | `https://api.rocksky.app` | `ROCKSKY_BASE_URL` |
129| `token` | `nil` | `ROCKSKY_TOKEN` |
130| `headers` | `{}` | — |
131| `user_agent` | `rocksky-ruby/<version>` | — |
132| `open_timeout` | `10` seconds | — |
133| `read_timeout` | `30` seconds | — |
134
135## IRB console
136
137```bash
138$ gem install rocksky
139$ ROCKSKY_TOKEN=eyJ... rocksky-console
140Rocksky 0.1.0 — interactive console
141 base_url : https://api.rocksky.app
142 token : present (set via ROCKSKY_TOKEN)
143
144irb> client.actor.get_profile(did: "did:plc:7vdlgi2bflelz7mmuxoqjfcr")
145=> {"did"=>"did:plc:...", "handle"=>"tsiry-sandratraina.com", ...}
146```
147
148## Types
149
150Lexicon-derived `Struct` types are available under `Rocksky::Generated::*`, mirroring every lex `*View*` / `*Record` / `*Input` / `*Output` / `*Params` shape from [the Rocksky lexicons](https://tangled.org/rocksky.app/rocksky/tree/main/apps/api/lexicons). Regenerate with `bun run lexgen:types` at the repo root.
151
152
153## License
154
155MIT © Tsiry Sandratraina. Source:
156[`sdk/ruby`](https://tangled.org/@rocksky.app/rocksky/blob/main/sdk/ruby).