diff --git a/crates/rocksky-sdk/README.md b/crates/rocksky-sdk/README.md
index d9f141ba..abccdf5d 100644
--- a/crates/rocksky-sdk/README.md
+++ b/crates/rocksky-sdk/README.md
@@ -73,7 +73,7 @@ Ok(()) }
| Verb | Record(s) |
| -------------------------------------- | ----------------------------- |
| `scrobble(draft)` → `ScrobbleResult` | fans out to **artist + album + song + scrobble** (duplicates skipped) |
-| `scrobble_match(title, artist, album, mb_id, isrc)` | resolves full metadata via `match_song`, then the same fan-out (album/mb_id/isrc optional) |
+| `scrobble_match(&ScrobbleMatch { .. })` | resolves full metadata via `match_song`, then the same fan-out (title/artist required; album/mb_id/isrc/timestamp optional) |
| `create_song(draft)` | `app.rocksky.song` |
| `create_album(draft)` | `app.rocksky.album` |
| `create_artist(draft)` | `app.rocksky.artist` |
diff --git a/docs/sdks/clojure.mdx b/docs/sdks/clojure.mdx
index dcba8cfe..c829d7af 100644
--- a/docs/sdks/clojure.mdx
+++ b/docs/sdks/clojure.mdx
@@ -13,7 +13,7 @@ identity hashes — the same engine behind every Rocksky SDK.
deps.edn:
```clojure
-app.rocksky/sdk {:mvn/version "0.5.0-SNAPSHOT"}
+app.rocksky/sdk {:mvn/version "0.6.0-SNAPSHOT"}
```
The jar is native-free; the library is fetched from the GitHub release on first
@@ -62,8 +62,10 @@ e.g. `(top-tracks-interval 5 0 [:days 7])`.
canonical metadata.
**Writes — `rocksky.core`**: `(login ... :dedup-path "./dedup")`, then
-`(scrobble agent track)` (full metadata) or `(scrobble-match agent title artist)`
-(optionally `... album mb-id isrc` — match-then-write), `(like agent uri cid)`,
+`(scrobble agent track)` (full metadata) or `(scrobble-match agent params)` — a
+map with camelCase keys, required `:title`/`:artist`, optional `:album`, `:mbId`,
+`:isrc` (match anchors) and `:timestamp` (scrobbled-at Unix seconds) —
+match-then-write, `(like agent uri cid)`,
`(follow agent did)`, `(shout ...)`, `(refresh-session agent)`,
`(agent-close agent)`. With `:dedup-path` set, `(sync-repo agent)` (repo backfill)
and `(hydrate-from-jetstream agent)` (live stream) run deduped against the
diff --git a/docs/sdks/elixir.mdx b/docs/sdks/elixir.mdx
index a2857f50..6f44834e 100644
--- a/docs/sdks/elixir.mdx
+++ b/docs/sdks/elixir.mdx
@@ -13,11 +13,11 @@ behind every Rocksky SDK. The same NIF powers the [Erlang](/sdks/erlang) and
```elixir
def deps do
- [{:rocksky_ex, "~> 0.5"}]
+ [{:rocksky_ex, "~> 0.6"}]
end
```
-`rocksky_ex` 0.5.0 pulls in `rocksky_erl` 0.2.0, whose loader fetches the native
+`rocksky_ex` 0.6.0 pulls in `rocksky_erl` 0.3.0, whose loader fetches the native
library from the GitHub release on first use.
## Quickstart
@@ -62,8 +62,12 @@ Typed date-window charts: `top_tracks_interval(5, 0, {:days, 7})` /
metadata.
**Writes — `Rocksky`**: `login(session, id, pw, appview \\ "", dedup_path \\ "")`,
-then `scrobble` (full metadata) or `scrobble_match(agent, title, artist, album
-\\ "", mb_id \\ "", isrc \\ "")` (match-then-write), `like`, `follow`, `shout`,
+then `scrobble` (full metadata) or `scrobble_match(agent, input)` — `input` is a
+map with camelCase string keys, required `"title"`/`"artist"`, optional
+`"album"`, `"mbId"`, `"isrc"` (match anchors) and `"timestamp"` (scrobbled-at
+Unix seconds), e.g.
+`Rocksky.scrobble_match(agent, %{"title" => "Chaser", "artist" => "Calibro 35"})`
+— then `like`, `follow`, `shout`,
`refresh_session`. Pass a `dedup_path` at login to enable dedup + realtime, then
`sync_repo(agent)` / `hydrate_from_jetstream(agent)`.
diff --git a/docs/sdks/erlang.mdx b/docs/sdks/erlang.mdx
index 710ba7b1..a5e32055 100644
--- a/docs/sdks/erlang.mdx
+++ b/docs/sdks/erlang.mdx
@@ -17,7 +17,7 @@ the GitHub release on first use.
```erlang
%% rebar.config
-{deps, [{rocksky_erl, "0.2.0"}]}.
+{deps, [{rocksky_erl, "0.3.0"}]}.
```
## Quickstart
@@ -34,7 +34,8 @@ io:format("~p scrobbles~n", [maps:get(<<"scrobbles">>, Stats)]),
%% Writes — log in with an app password.
Agent = rocksky:agent_login(<<"session.json">>, <<"alice.bsky.social">>, <<"app-pw">>),
-{ok, Out} = rocksky:agent_scrobble_match(Agent, <<"Chaser">>, <<"Calibro 35">>).
+{ok, Out} = rocksky:agent_scrobble_match(Agent,
+ #{<<"title">> => <<"Chaser">>, <<"artist">> => <<"Calibro 35">>}).
```
Reads/writes return `{ok, Value}` | `{error, Message}` with binary-keyed maps.
@@ -72,9 +73,13 @@ into full canonical metadata (album, artwork, duration, MBID, ISRC, links).
then `agent_scrobble` (full metadata), `agent_like`, `agent_follow`,
`agent_shout`, `agent_refresh_session`.
-**Match-then-scrobble** — `rocksky:agent_scrobble_match(Agent, Title, Artist)`
-(also `/4` with `Album`, `/6` with `Album`, `MbId`, `Isrc`) resolves canonical
-metadata and scrobbles in one call.
+**Match-then-scrobble** — `rocksky:agent_scrobble_match(Agent, Input)` where
+`Input` is a map with camelCase binary keys: required `<<"title">>`/`<<"artist">>`,
+optional `<<"album">>`, `<<"mbId">>`, `<<"isrc">>` (match anchors) and
+`<<"timestamp">>` (scrobbled-at Unix seconds). Resolves canonical metadata and
+scrobbles in one call. (A flat `/7` form —
+`agent_scrobble_match(Agent, Title, Artist, Album, MbId, Isrc, Timestamp)`, empty
+strings / `0` omitted — backs the Gleam SDK.)
**Dedup + realtime** — pass a `DedupPath` to `agent_login/5` to enable the local
dedup store, then keep it warm with `rocksky:agent_sync_repo(Agent)` and
diff --git a/docs/sdks/gleam.mdx b/docs/sdks/gleam.mdx
index dae4ef5e..49dab0c1 100644
--- a/docs/sdks/gleam.mdx
+++ b/docs/sdks/gleam.mdx
@@ -11,10 +11,10 @@ PDS writes, and the identity hashes. Targets Erlang.
## Install
```bash
-gleam add rocksky # rocksky = ">= 1.5.0 and < 2.0.0"
+gleam add rocksky # rocksky = ">= 1.6.0 and < 2.0.0"
```
-`rocksky` 1.5.0 depends on `rocksky_erl` 0.2.0, whose loader fetches the native
+`rocksky` 1.6.0 depends on `rocksky_erl` 0.3.0, whose loader fetches the native
library from the GitHub release on first use.
## Quickstart
@@ -52,8 +52,12 @@ LastYears(Int) | Range(String, String)`. `match_song(title, artist)` resolves a
bare title + artist into full canonical metadata.
**Writes**: `login(session_path, identifier, password)`, then `scrobble` (full
-metadata) or `scrobble_match(agent, title, artist, album, mb_id, isrc)`
-(match-then-write), `like`, `follow`, `shout`, `refresh_session`. With a dedup path
+metadata) or `scrobble_match(agent, ScrobbleMatch(title, artist, album, mb_id,
+isrc, timestamp))` (match-then-write) — `title`/`artist` are `String`, the rest
+`Option` (`album` override, `mb_id`/`isrc` match anchors, `timestamp` scrobbled-at
+Unix seconds), e.g.
+`scrobble_match(agent, ScrobbleMatch("Chaser", "Calibro 35", None, None, None, None))` —
+`like`, `follow`, `shout`, `refresh_session`. With a dedup path
set, `sync_repo(agent)` / `hydrate_from_jetstream(agent)` keep the dedup + realtime
store warm.
diff --git a/docs/sdks/go.mdx b/docs/sdks/go.mdx
index 9c674dfd..eaf1164f 100644
--- a/docs/sdks/go.mdx
+++ b/docs/sdks/go.mdx
@@ -61,8 +61,9 @@ auth-gated queries with `NewClient("").WithToken(token)`.
`TopTracks` / `TopArtists` remain all-time shorthands.
**Writes — `Agent`**: `Scrobble` (fan-out from full metadata),
-`ScrobbleMatch(ctx, appview, title, artist, album, mbID, isrc)` (resolves
-metadata via `MatchSong`, then the same fan-out; album/mbID/isrc optional),
+`ScrobbleMatch(ctx, appview, ScrobbleMatchInput{Title, Artist})` (resolves
+metadata via `MatchSong`, then the same fan-out; Title/Artist required,
+Album/MbID/ISRC/Timestamp optional pointers, `appview` "" = default),
`CreateSong` / `CreateAlbum` / `CreateArtist`, `Like`, `Follow`, `Shout` /
`ReplyShout`, `SetNowPlaying` / `ClearNowPlaying`, `Delete`, `RefreshSession`.
Records are the generated `gen.*Record` types.
diff --git a/docs/sdks/kotlin.mdx b/docs/sdks/kotlin.mdx
index 32cb9a44..b65ca80a 100644
--- a/docs/sdks/kotlin.mdx
+++ b/docs/sdks/kotlin.mdx
@@ -13,7 +13,7 @@ Rocksky SDK.
```kotlin
dependencies {
- implementation("app.rocksky:rocksky-kotlin:0.6.0")
+ implementation("app.rocksky:rocksky-kotlin:0.7.0")
}
```
@@ -56,8 +56,11 @@ metadata. A raw-JSON long tail (`album`, `artist`, `song`, `playlists`,
calls **any** read query by NSID.
**Writes — `Agent`**: `login(...)` (top-level, `appview`/`dedupPath` optional),
-then `scrobble` (full metadata) or `scrobbleMatch(title, artist, album, mbId,
-isrc)` (resolve via `matchSong`, then fan out), `createSong` / `createAlbum` /
+then `scrobble` (full metadata) or `scrobbleMatch(ScrobbleMatchInput(title,
+artist, album, mbId, isrc, timestamp))` (resolve via `matchSong`, then fan out —
+`title`/`artist` required, `album`/`mbId`/`isrc` are `String?` and `timestamp`
+is `Long?`, all default null; `ScrobbleMatchInput` is an `app.rocksky`
+typealias), `createSong` / `createAlbum` /
`createArtist`, `like` / `unlike`, `follow` / `unfollow`, `shout` / `replyShout`,
`setNowPlaying` / `clearNowPlaying`, `syncRepo` (dedup backfill, returns
per-collection counts as JSON), `hydrateFromJetstream` (live dedup off the
diff --git a/docs/sdks/overview.mdx b/docs/sdks/overview.mdx
index 1cc84fb2..1133a4ee 100644
--- a/docs/sdks/overview.mdx
+++ b/docs/sdks/overview.mdx
@@ -66,7 +66,9 @@ Every SDK exposes the same three things:
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 bare title + artist), `createSong` / `createAlbum` /
+ (match-then-write from a title/artist input object, with optional album
+ override, `mbId`/`isrc` match 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.
diff --git a/docs/sdks/python.mdx b/docs/sdks/python.mdx
index 0ae4b7be..849fa458 100644
--- a/docs/sdks/python.mdx
+++ b/docs/sdks/python.mdx
@@ -12,11 +12,11 @@ hashes — the same engine behind every Rocksky SDK.
```bash uv
-uv add rocksky==0.5.0
+uv add rocksky==0.6.0
```
```bash pip
-pip install rocksky==0.5.0
+pip install rocksky==0.6.0
```
@@ -61,8 +61,10 @@ bare title+artist to canonical metadata. A raw-JSON long tail (`album`, `artist`
`av.get(nsid, params)` (a `dict[str, str]`) calls **any** read query by NSID.
**Writes — `Agent`**: `login_password(...)`, then `scrobble` (full metadata) or
-`scrobble_match(title, artist, album, mb_id, isrc)` (resolve via `match_song`,
-then fan out), `create_song` / `create_album` / `create_artist`, `like` /
+`scrobble_match(ScrobbleMatchInput(title, artist, album=None, mb_id=None,
+isrc=None, timestamp=None))` (resolve via `match_song`, then fan out — `title`/
+`artist` required, `timestamp` is scrobbled-at Unix seconds; `ScrobbleMatchInput`
+imports from `rocksky`), `create_song` / `create_album` / `create_artist`, `like` /
`unlike`, `follow` / `unfollow`, `shout` / `reply_shout`, `set_now_playing` /
`clear_now_playing`, `sync_repo` (dedup backfill, returns per-collection counts
as JSON), `hydrate_from_jetstream` (live dedup off the firehose).
diff --git a/docs/sdks/ruby.mdx b/docs/sdks/ruby.mdx
index 653a6e36..0e5c3971 100644
--- a/docs/sdks/ruby.mdx
+++ b/docs/sdks/ruby.mdx
@@ -14,7 +14,7 @@ same engine behind every Rocksky SDK.
gem install rocksky
```
-Or in a Gemfile: `gem "rocksky", "~> 0.5"`.
+Or in a Gemfile: `gem "rocksky", "~> 0.6"`.
The gem is pure-Ruby; the native library is fetched from the GitHub release on
first load and cached.
@@ -64,8 +64,10 @@ and `top_artists_interval(...)` — `interval:` is `:all` or `[:days, n]` /
bare title + artist into full canonical metadata.
**Writes — `Rocksky::Agent`**: `login(..., dedup_path:)`, then `scrobble`
-(full metadata) or `scrobble_match(title, artist, album: nil, mb_id: nil, isrc: nil)`
-(match-then-write), plus `like`, `follow`, `shout`, `refresh_session`, `close`.
+(full metadata) or `scrobble_match(params)` — a single Hash with camelCase
+string keys, required `"title"`/`"artist"`, optional `"album"`, `"mbId"`,
+`"isrc"`, `"timestamp"` (match-then-write), plus `like`, `follow`, `shout`,
+`refresh_session`, `close`.
With `dedup_path:` set at login, `sync_repo` (repo backfill) and
`hydrate_from_jetstream` (live stream) run deduped against the on-disk store.
diff --git a/docs/sdks/rust.mdx b/docs/sdks/rust.mdx
index b298b48b..27364249 100644
--- a/docs/sdks/rust.mdx
+++ b/docs/sdks/rust.mdx
@@ -14,7 +14,7 @@ index and Jetstream real-time sync.
```toml
[dependencies]
-rocksky-sdk = { version = "0.2", features = ["dedup", "jetstream"] }
+rocksky-sdk = { version = "0.4", features = ["dedup", "jetstream"] }
```
The `dedup` and `jetstream` features are optional (they pull in RocksDB + the
@@ -69,8 +69,9 @@ let month = av.top_tracks_interval(DateInterval::LastDays(30), 50, 0).await?;
```
**Writes — `RockskyAgent`**: `scrobble` (fan-out from full metadata),
-`scrobble_match(title, artist, album, mb_id, isrc)` (resolves metadata via
-`match_song`, then the same fan-out; album/mb_id/isrc optional), `create_song` /
+`scrobble_match(&ScrobbleMatch { .. })` (resolves metadata via `match_song`, then
+the same fan-out; title/artist required, album/mb_id/isrc/timestamp optional),
+`create_song` /
`create_album` / `create_artist`, `like` / `unlike`, `follow` / `unfollow`,
`shout` / `reply_shout`, `set_now_playing` / `clear_now_playing`. App-password +
loopback OAuth login.
diff --git a/docs/sdks/typescript.mdx b/docs/sdks/typescript.mdx
index 817799e5..b315b78b 100644
--- a/docs/sdks/typescript.mdx
+++ b/docs/sdks/typescript.mdx
@@ -84,8 +84,13 @@ full canonical metadata (album, artwork, duration, MBID, ISRC, links).
`new RockskyClient(appview, token)` — sent as `Authorization: Bearer `.
**Writes — `Agent`**: two scrobble paths — `scrobble(rec)` writes full metadata
-you already have, and `scrobbleMatch(title, artist, album?, mbId?, isrc?)`
-resolves full metadata via `matchSong` first, then writes. Plus `createSong` /
+you already have, and `scrobbleMatch(input, appview?)` takes a
+`ScrobbleMatchInput` object (`{ title, artist, album?, mbId?, isrc?, timestamp? }`
+— title/artist required; `album` overrides the resolved album, `mbId`/`isrc` are
+match anchors, `timestamp` is a scrobbled-at Unix-seconds time; the optional
+`appview` overrides the AppView used for matching) and resolves full metadata via
+`matchSong` first, then writes — e.g.
+`agent.scrobbleMatch({ title: "Chaser", artist: "Calibro 35" })`. Plus `createSong` /
`createAlbum` / `createArtist`, `like`, `follow`, `shout` / `replyShout`,
`setNowPlaying` / `clearNowPlaying`, `delete`.
diff --git a/sdk/clojure/README.md b/sdk/clojure/README.md
index b5d3ce39..9efc444b 100644
--- a/sdk/clojure/README.md
+++ b/sdk/clojure/README.md
@@ -12,7 +12,7 @@ same engine behind every Rocksky SDK.
deps.edn:
```clojure
-app.rocksky/sdk {:mvn/version "0.5.0-SNAPSHOT"}
+app.rocksky/sdk {:mvn/version "0.6.0-SNAPSHOT"}
```
The jar is native-free; the library is fetched from the GitHub release on first
@@ -89,8 +89,9 @@ canonical metadata.
`(login session-path identifier password)` → an opaque agent handle; pass
`:dedup-path "./dedup"` to enable scrobble dedup + realtime hydration (see below).
Then `(scrobble agent track)` (full metadata, fans out to
-artist/album/song/scrobble) or `(scrobble-match agent title artist)` (optionally
-`... album mb-id isrc` — matches a bare title + artist first, then writes),
+artist/album/song/scrobble) or `(scrobble-match agent params)` — a map with
+camelCase keys, required `:title`/`:artist`, optional `:album`, `:mbId`, `:isrc`,
+`:timestamp` — matching a bare title + artist first, then writes,
`(like agent uri cid)`, `(follow agent did)`,
`(shout agent subject-uri subject-cid message)`, `(refresh-session agent)`,
`(agent-close agent)`.
diff --git a/sdk/elixir/README.md b/sdk/elixir/README.md
index 454a26c2..3e61ee43 100644
--- a/sdk/elixir/README.md
+++ b/sdk/elixir/README.md
@@ -16,11 +16,11 @@ every Rocksky SDK.
```elixir
def deps do
- [{:rocksky_ex, "~> 0.5"}]
+ [{:rocksky_ex, "~> 0.6"}]
end
```
-`rocksky_ex` 0.5.0 pulls in `rocksky_erl` 0.2.0, whose loader fetches the native library from
+`rocksky_ex` 0.6.0 pulls in `rocksky_erl` 0.3.0, whose loader fetches the native library from
the GitHub release on first use (checksum-verified). For monorepo dev, build it
locally and point at it: `../erlang/build-core.sh` then set
`ROCKSKY_ERL_PATH=../erlang`.
@@ -89,9 +89,12 @@ opaque agent handle. Then `scrobble(agent, track)` (full metadata; fans out to
artist/album/song/scrobble), `like(agent, uri, cid)`, `follow(agent, did)`,
`shout(agent, subject_uri, subject_cid, message)`, `refresh_session(agent)`.
-**Match-then-scrobble** — `scrobble_match(agent, title, artist, album \\ "", mb_id
-\\ "", isrc \\ "")` resolves canonical metadata and scrobbles in one call; the
-full-metadata `scrobble/2` still works when you already have it.
+**Match-then-scrobble** — `scrobble_match(agent, input)` resolves canonical
+metadata and scrobbles in one call. `input` is a map with camelCase string keys:
+required `"title"`/`"artist"`, optional `"album"` (override), `"mbId"`/`"isrc"`
+(match anchors) and `"timestamp"` (scrobbled-at Unix seconds, default now) —
+e.g. `scrobble_match(agent, %{"title" => "Chaser", "artist" => "Calibro 35"})`.
+The full-metadata `scrobble/2` still works when you already have it.
**Dedup + realtime** — pass a `dedup_path` to `login/5` to enable the local dedup
store, then keep it warm with `sync_repo(agent)` and
diff --git a/sdk/erlang/README.md b/sdk/erlang/README.md
index 719d76f8..eac5df1f 100644
--- a/sdk/erlang/README.md
+++ b/sdk/erlang/README.md
@@ -37,7 +37,7 @@ Agent = rocksky:agent_login(<<"session.json">>, <<"alice.bsky.social">>, <<"app-
Reads/writes return `{ok, Value}` | `{error, Message}` with binary-keyed maps.
-The Hex package is `rocksky_erl` 0.2.0.
+The Hex package is `rocksky_erl` 0.3.0.
## API
@@ -70,9 +70,13 @@ into full canonical metadata.
then `agent_scrobble` (full metadata), `agent_like`, `agent_follow`,
`agent_shout`, `agent_refresh_session`.
-**Match-then-scrobble** — `rocksky:agent_scrobble_match(Agent, Title, Artist)`
-(also `/4` with `Album`, `/6` with `Album`, `MbId`, `Isrc`) resolves canonical
-metadata and scrobbles in one call.
+**Match-then-scrobble** — `rocksky:agent_scrobble_match(Agent, Input)` where
+`Input` is a map with camelCase binary keys: required `<<"title">>`/`<<"artist">>`,
+optional `<<"album">>`, `<<"mbId">>`, `<<"isrc">>` (match anchors) and
+`<<"timestamp">>` (scrobbled-at Unix seconds). Resolves canonical metadata and
+scrobbles in one call. A flat `/7` form —
+`agent_scrobble_match(Agent, Title, Artist, Album, MbId, Isrc, Timestamp)`, empty
+strings / `0` omitted — backs the Gleam SDK.
**Dedup + realtime** — pass a `DedupPath` to `agent_login/5` to enable the local
dedup store, then keep it warm with `rocksky:agent_sync_repo(Agent)` and
diff --git a/sdk/gleam/README.md b/sdk/gleam/README.md
index 9a6546b2..c883f111 100644
--- a/sdk/gleam/README.md
+++ b/sdk/gleam/README.md
@@ -15,10 +15,10 @@ Rocksky SDK. Targets Erlang.
## Installation
```sh
-gleam add rocksky # rocksky = ">= 1.5.0 and < 2.0.0"
+gleam add rocksky # rocksky = ">= 1.6.0 and < 2.0.0"
```
-`rocksky` 1.5.0 depends on `rocksky_erl` 0.2.0, whose loader fetches the native
+`rocksky` 1.6.0 depends on `rocksky_erl` 0.3.0, whose loader fetches the native
library from the GitHub release on first use (checksum-verified). For monorepo
dev, build it with `../erlang/build-core.sh` and use the local path dep in
`gleam.toml`.
@@ -78,7 +78,10 @@ canonical metadata.
`login(session_path, identifier, password)` → an opaque `Agent`. Then
`scrobble(agent, track)` (full metadata) or
-`scrobble_match(agent, title, artist, album, mb_id, isrc)` (match-then-write),
+`scrobble_match(agent, ScrobbleMatch(title, artist, album, mb_id, isrc, timestamp))`
+(match-then-write) — `title`/`artist` are `String`, the rest `Option` (`album`
+override, `mb_id`/`isrc` match anchors, `timestamp` scrobbled-at Unix seconds),
+e.g. `scrobble_match(agent, ScrobbleMatch("Chaser", "Calibro 35", None, None, None, None))`,
`like(agent, uri, cid)`, `follow(agent, did)`,
`shout(agent, subject_uri, subject_cid, message)`, `refresh_session(agent)`.
diff --git a/sdk/go/README.md b/sdk/go/README.md
index f73dfe8f..aa25661f 100644
--- a/sdk/go/README.md
+++ b/sdk/go/README.md
@@ -62,8 +62,9 @@ Attach a bearer token for auth-gated queries with `NewClient("").WithToken(token
`TopTracks` / `TopArtists` remain all-time shorthands.
**Writes — `Agent`**: `Scrobble` (fan-out from full metadata),
-`ScrobbleMatch(ctx, appview, title, artist, album, mbID, isrc)` (resolves
-metadata via `MatchSong`, then the same fan-out; album/mbID/isrc optional),
+`ScrobbleMatch(ctx, appview, ScrobbleMatchInput{Title, Artist})` (resolves
+metadata via `MatchSong`, then the same fan-out; Title/Artist required,
+Album/MbID/ISRC/Timestamp optional pointers, `appview` "" = default),
`CreateSong`/`CreateAlbum`/`CreateArtist`, `Like`, `Follow`, `Shout`/`ReplyShout`,
`SetNowPlaying`/`ClearNowPlaying`, `Delete`, `RefreshSession`. Records are the
generated `gen.*Record` types.
diff --git a/sdk/kotlin/README.md b/sdk/kotlin/README.md
index 298c27bc..780d4f08 100644
--- a/sdk/kotlin/README.md
+++ b/sdk/kotlin/README.md
@@ -10,7 +10,7 @@ every Rocksky SDK. Lives in the `:rocksky` Gradle module, package `app.rocksky`.
```kotlin
dependencies {
- implementation("app.rocksky:rocksky-kotlin:0.6.0")
+ implementation("app.rocksky:rocksky-kotlin:0.7.0")
}
```
@@ -79,9 +79,13 @@ returns an agent (`appview`/`dedupPath` default to none). Then:
- `scrobble(ScrobbleInput)` — from full metadata, writes the scrobble; with a
dedup store, skips an existing same-second scrobble of the same track.
-- `scrobbleMatch(title, artist, album, mbId, isrc)` — resolves the track via
- `matchSong` first, then fans out the same way (`album` / `mbId` / `isrc`
- optional).
+- `scrobbleMatch(ScrobbleMatchInput)` — resolves the track via `matchSong`
+ first, then fans out the same way. `ScrobbleMatchInput(title, artist, album,
+ mbId, isrc, timestamp)` — `title`/`artist` required; `album`/`mbId`/`isrc` are
+ `String?` (album override, match anchors) and `timestamp` is `Long?`
+ (scrobbled-at Unix seconds), all defaulting to null. `ScrobbleMatchInput` is
+ an `app.rocksky` typealias
+ (e.g. `agent.scrobbleMatch(ScrobbleMatchInput(title = "Chaser", artist = "Calibro 35"))`).
- `createSong(SongInput)`, `createAlbum(AlbumInput)`, `createArtist(ArtistInput)`
- `like(uri, cid)`, `unlike(uri)`, `follow(did)`, `unfollow(did)`
- `shout(subjectUri, subjectCid, message)`, `replyShout(...)`
diff --git a/sdk/python/README.md b/sdk/python/README.md
index 283a4ac9..ce51d26f 100644
--- a/sdk/python/README.md
+++ b/sdk/python/README.md
@@ -9,7 +9,7 @@ every Rocksky SDK (Ruby, Kotlin, Clojure, Erlang/Elixir/Gleam).
## Install
```sh
-pip install rocksky==0.5.0 # or: uv add rocksky==0.5.0
+pip install rocksky==0.6.0 # or: uv add rocksky==0.6.0
```
The wheel is pure-Python; the ~14 MB native library is fetched from the GitHub
@@ -87,9 +87,12 @@ returns an agent. Then:
- `scrobble(ScrobbleInput)` → `ScrobbleResult` — from full metadata, writes
**artist + album + song + scrobble**, skipping any that already exist (with a
dedup store).
-- `scrobble_match(title, artist, album, mb_id, isrc)` → `ScrobbleResult` —
- resolves the track via `match_song` first, then fans out the same way
- (`album` / `mb_id` / `isrc` optional).
+- `scrobble_match(ScrobbleMatchInput)` → `ScrobbleResult` — resolves the track
+ via `match_song` first, then fans out the same way. `ScrobbleMatchInput(title,
+ artist, album=None, mb_id=None, isrc=None, timestamp=None)` — `title`/`artist`
+ required; `album` overrides the resolved album, `mb_id`/`isrc` are match
+ anchors, `timestamp` is the scrobbled-at Unix seconds
+ (e.g. `agent.scrobble_match(ScrobbleMatchInput(title="Chaser", artist="Calibro 35"))`).
- `create_song(SongInput)`, `create_album(AlbumInput)`, `create_artist(ArtistInput)`
- `like(uri, cid)`, `unlike(uri)`, `follow(did)`, `unfollow(did)`
- `shout(subject_uri, subject_cid, message)`, `reply_shout(...)`
diff --git a/sdk/ruby/README.md b/sdk/ruby/README.md
index 7973c705..b437df05 100644
--- a/sdk/ruby/README.md
+++ b/sdk/ruby/README.md
@@ -14,7 +14,7 @@ gem install rocksky
Or in a Gemfile:
```ruby
-gem "rocksky", "~> 0.5"
+gem "rocksky", "~> 0.6"
```
The gem is pure-Ruby; the native library is fetched from the GitHub release on
@@ -92,11 +92,14 @@ below). Then `scrobble(track)` (fans out to artist/album/song/scrobble),
`refresh_session`, and `close` (release the native handle).
**Two scrobble paths** — `scrobble(track)` takes full metadata, while
-`scrobble_match(title, artist, album: nil, mb_id: nil, isrc: nil)` matches a bare
+`scrobble_match(params)` takes a single Hash with camelCase string keys —
+required `"title"`/`"artist"`, optional `"album"` (override), `"mbId"`/`"isrc"`
+(match anchors), `"timestamp"` (scrobbled-at Unix seconds) — matching a bare
title + artist first, then writes.
```ruby
-agent.scrobble_match("Chaser", "Calibro 35", album: "Jazzploitation")
+agent.scrobble_match("title" => "Chaser", "artist" => "Calibro 35",
+ "album" => "Jazzploitation")
```
**Dedup + realtime** — with `dedup_path:` set at login, `agent.sync_repo`
diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md
index 225073f4..fdd4c96a 100644
--- a/sdk/typescript/README.md
+++ b/sdk/typescript/README.md
@@ -69,8 +69,13 @@ full canonical metadata (album, artwork, duration, MBID, ISRC, links).
`Authorization: Bearer `.
**Writes — `Agent`**: two scrobble paths. `scrobble(rec)` writes full metadata
-you already have; `scrobbleMatch(title, artist, album?, mbId?, isrc?)` resolves
-full metadata via `matchSong` first, then writes. Plus
+you already have; `scrobbleMatch(input, appview?)` takes a `ScrobbleMatchInput`
+object (`{ title, artist, album?, mbId?, isrc?, timestamp? }` — title/artist
+required; `album` overrides the resolved album, `mbId`/`isrc` are match anchors,
+`timestamp` is a scrobbled-at Unix-seconds time; the optional `appview` overrides
+the AppView used for matching) and resolves full metadata via `matchSong` first,
+then writes — e.g. `agent.scrobbleMatch({ title: "Chaser", artist: "Calibro 35" })`.
+Plus
`createSong`/`createAlbum`/`createArtist`, `like`, `follow`,
`shout`/`replyShout`, `setNowPlaying`/`clearNowPlaying`, `delete`. Records are the
generated types from `./generated/types`.