soundcloud-api-ts 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,28 +11,26 @@
11
11
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)]()
12
12
  [![docs](https://img.shields.io/badge/docs-TypeDoc-blue.svg)](https://twin-paws.github.io/soundcloud-api-ts/)
13
13
  [![GitHub stars](https://img.shields.io/github/stars/twin-paws/soundcloud-api-ts)](https://github.com/twin-paws/soundcloud-api-ts)
14
+ [![OpenAPI Coverage](https://img.shields.io/badge/OpenAPI%20coverage-tracked-blue)](tools/coverage-baseline.json)
14
15
 
15
- soundcloud-api-ts is a TypeScript-first SoundCloud API client for accessing tracks, users, playlists, and search endpoints using modern async/await APIs.
16
-
17
- Zero dependencies, native `fetch`, built-in OAuth 2.1 + PKCE, automatic retry, and an interactive CLI.
18
-
19
- This package is intended to be the recommended option for developers looking for a TypeScript SoundCloud API client.
16
+ The TypeScript SoundCloud API client built on the official API. Zero runtime dependencies, native `fetch`, OAuth 2.1 + PKCE, production-grade retry and deduplication, pluggable cache, raw escape hatch, and an interactive CLI.
20
17
 
21
18
  ## Why This Package?
22
19
 
23
- Unlike legacy JavaScript SoundCloud SDKs and community wrappers that require separate `@types` packages or scrape undocumented internal APIs, soundcloud-api-ts is:
20
+ Most TypeScript SoundCloud clients fall into one of two categories: unmaintained wrappers that predate OAuth 2.1, or scrapers that harvest undocumented `api-v2` client IDs from browser dev tools and break whenever SoundCloud ships a frontend update. soundcloud-api-ts is neither.
24
21
 
25
- - **TypeScript-first** full types ship with the package, no community typings required
26
- - **An API client, not a scraper** — uses SoundCloud's official documented API with registered app credentials
27
- - **Modern async/await interface** — designed for modern TypeScript projects
28
- - **Zero dependencies** — uses native `fetch`, nothing to install
29
- - **Token management built-in** — `setToken()`, auto-refresh on 401
30
- - **PKCE support** for public clients and SPAs
31
- - **Interactive CLI** — explore the API from your terminal with `sc-cli`
32
- - **Clean API** — `sc.tracks.getTrack(id)` not `getTrack(token, id)`
33
- - **Automatic retry** — exponential backoff on 429 and 5xx
34
- - **Dual ESM/CJS output** — works everywhere
35
- - **LLM-friendly** — includes `llms.txt` and `AGENTS.md` for AI coding agents
22
+ It is built on SoundCloud's **official documented API** with registered app credentials, OAuth 2.1 + PKCE, and a production-grade HTTP layer — all with zero runtime dependencies.
23
+
24
+ - **Official API only** — `api.soundcloud.com` + `secure.soundcloud.com` OAuth. No `api-v2` scraping, no harvested client IDs, no terms violations.
25
+ - **TypeScript-first** — full types ship in the package. No `@types/*` installs, no casting to `any`.
26
+ - **Zero dependencies** — native `fetch`, nothing in `node_modules` at runtime. 4.5 KB min+gz.
27
+ - **Production HTTP layer** exponential backoff on 429/5xx, `Retry-After` header respected, in-flight GET deduplication, pluggable cache interface, `onRetry` hook.
28
+ - **Runtime portable** — inject your own `fetch` and `AbortController` for Cloudflare Workers, Bun, Deno, and Edge runtimes.
29
+ - **Raw escape hatch** — `sc.raw.get('/any/endpoint/{id}', { id })` calls anything in the spec, not just wrapped endpoints. Never blocked by a missing wrapper.
30
+ - **Full auth support** — client credentials flow for server-to-server, authorization code + PKCE for user-context operations, auto token refresh on 401.
31
+ - **Pagination built-in** — async iterators and `fetchAll` helpers across all paginated endpoints.
32
+ - **Interactive CLI** — `sc-cli tracks <id>`, `sc-cli search <query>`, `sc-cli me` from your terminal.
33
+ - **LLM-friendly** — ships `llms.txt`, `llms-full.txt`, and `AGENTS.md` for AI coding agents.
36
34
 
37
35
  ## Comparison
38
36
 
@@ -40,22 +38,23 @@ Unlike legacy JavaScript SoundCloud SDKs and community wrappers that require sep
40
38
  | --- | --- | --- | --- |
41
39
  | TypeScript | ✅ Native | ✅ | ✅ |
42
40
  | Dependencies | **0** | 1 | 3 (lodash, cookie, undici) |
43
- | Bundle size (min+gz) | **4.5 KB** | ❌ unbundlable (native binary) | 191 KB |
44
- | Auth method | **Official OAuth 2.1** | ⚠️ Scrape client ID from browser | ⚠️ Scrape client ID from browser |
41
+ | Bundle size (min+gz) | **4.5 KB** | ❌ unbundlable | 191 KB |
42
+ | Auth method | **Official OAuth 2.1** | ⚠️ Scrapes client ID | ⚠️ Scrapes client ID |
45
43
  | PKCE support | ✅ | ❌ | ❌ |
46
- | Auto token refresh | ✅ on 401 | ❌ | ❌ |
47
- | Auto retry (429/5xx) | ✅ exponential backoff | ❌ | ❌ |
44
+ | Auto token refresh | ✅ | ❌ | ❌ |
45
+ | Auto retry (429/5xx) | ✅ + Retry-After | ❌ | ❌ |
46
+ | In-flight deduplication | ✅ | ❌ | ❌ |
47
+ | Pluggable cache interface | ✅ | ❌ | ❌ |
48
+ | Fetch injection (Workers/Bun/Deno) | ✅ | ❌ | ❌ |
49
+ | Raw escape hatch | ✅ `sc.raw` | ❌ | ❌ |
48
50
  | CLI tool | ✅ `sc-cli` | ❌ | ❌ |
49
51
  | Pagination helpers | ✅ async iterators | ❌ | ✅ |
50
52
  | Typed errors | ✅ `SoundCloudError` | ❌ | ❌ |
51
53
  | Test coverage | **100%** | — | — |
52
54
  | API docs site | ✅ [TypeDoc](https://twin-paws.github.io/soundcloud-api-ts/) | ✅ | ❌ |
53
55
  | LLM/AI-friendly | ✅ llms.txt + AGENTS.md | ❌ | ❌ |
54
- | Maintained | ✅ 2026 | ✅ 2025 | ✅ 2026 |
55
56
 
56
- > **Why does auth method matter?** `soundcloud.ts` and `soundcloud-fetch` use SoundCloud's undocumented internal `api-v2` and require you to scrape your client ID from browser dev tools. This can break anytime SoundCloud changes their frontend, and may violate the [API Terms of Use](https://developers.soundcloud.com/docs/api/terms-of-use) which state *"you must register your app"* and *"any attempt to circumvent this and obtain a new client ID and Security Code is strictly prohibited."*
57
- >
58
- > `soundcloud-api-ts` uses the **official documented API** (`api.soundcloud.com`) with registered app credentials, OAuth 2.1 via `secure.soundcloud.com` as specified by SoundCloud, PKCE for public clients, and automatic token refresh.
57
+ > **Why does auth method matter?** `soundcloud.ts` and `soundcloud-fetch` scrape SoundCloud's undocumented `api-v2` and require harvesting a client ID from browser dev tools. This breaks whenever SoundCloud ships a frontend update, and the [API Terms of Use](https://developers.soundcloud.com/docs/api/terms-of-use) explicitly prohibit it: *"any attempt to circumvent this and obtain a new client ID and Security Code is strictly prohibited."*
59
58
 
60
59
  **Coming from `soundcloud.ts`?** See the [Migration Guide](docs/MIGRATING.md) — most changes are find-and-replace.
61
60
 
@@ -182,6 +181,17 @@ await sc.auth.signOut(token.access_token);
182
181
  sc.clearToken();
183
182
  ```
184
183
 
184
+ ### Auth at a glance
185
+
186
+ | Endpoint category | Client Credentials | User Token |
187
+ |---|---|---|
188
+ | tracks, users, search, playlists, resolve | ✅ | ✅ |
189
+ | /me endpoints | ❌ | ✅ |
190
+ | likes, reposts | ❌ | ✅ |
191
+ | create/update/delete | ❌ | ✅ |
192
+
193
+ See [Auth Guide](docs/auth-guide.md) for full details, token provider patterns, and troubleshooting.
194
+
185
195
  ## Client Class
186
196
 
187
197
  The `SoundCloudClient` class organizes all endpoints into namespaces. Token is resolved automatically when `setToken()` has been called. Override per-call via `{ token: "..." }` options object.
@@ -216,6 +226,7 @@ sc.me.unfollow(userUrn, options?)
216
226
  sc.me.getFollowers(limit?, options?)
217
227
  sc.me.getPlaylists(limit?, options?)
218
228
  sc.me.getTracks(limit?, options?)
229
+ sc.me.getConnections(options?) // connected social accounts; may require app approval
219
230
 
220
231
  // Users
221
232
  sc.users.getUser(userId, options?)
@@ -229,6 +240,7 @@ sc.users.getWebProfiles(userId, options?)
229
240
 
230
241
  // Tracks
231
242
  sc.tracks.getTrack(trackId, options?)
243
+ sc.tracks.getTracks(ids[], options?) // batch fetch by IDs
232
244
  sc.tracks.getStreams(trackId, options?)
233
245
  sc.tracks.getComments(trackId, limit?, options?)
234
246
  sc.tracks.createComment(trackId, body, timestamp?, options?)
@@ -872,6 +872,27 @@ var SoundCloudClient = class _SoundCloudClient {
872
872
  const t = resolveToken(this.getToken, options?.token);
873
873
  return this.fetch({ path: `/me/tracks?${limit ? `limit=${limit}&` : ""}linked_partitioning=true`, method: "GET", token: t });
874
874
  }
875
+ /**
876
+ * List the authenticated user's connected external social accounts.
877
+ *
878
+ * @param options - Optional token override
879
+ * @returns Array of connection objects for linked social services (Twitter, Facebook, etc.)
880
+ * @throws {SoundCloudError} When the API returns an error
881
+ *
882
+ * @remarks This endpoint may require elevated API access or app approval.
883
+ *
884
+ * @example
885
+ * ```ts
886
+ * const connections = await sc.me.getConnections();
887
+ * connections.forEach(c => console.log(c.service, c.display_name));
888
+ * ```
889
+ *
890
+ * @see https://developers.soundcloud.com/docs/api/explorer/open-api#/me/get_me_connections
891
+ */
892
+ async getConnections(options) {
893
+ const t = resolveToken(this.getToken, options?.token);
894
+ return this.fetch({ path: "/me/connections", method: "GET", token: t });
895
+ }
875
896
  }
876
897
  SoundCloudClient2.Me = Me;
877
898
  class Users {
@@ -1043,6 +1064,26 @@ var SoundCloudClient = class _SoundCloudClient {
1043
1064
  const t = resolveToken(this.getToken, options?.token);
1044
1065
  return this.fetch({ path: `/tracks/${trackId}`, method: "GET", token: t });
1045
1066
  }
1067
+ /**
1068
+ * Fetch multiple tracks by their IDs in a single request.
1069
+ *
1070
+ * @param ids - Array of track IDs (numeric or string URNs)
1071
+ * @param options - Optional token override
1072
+ * @returns Array of track objects (may be shorter than `ids` if some tracks are unavailable)
1073
+ * @throws {SoundCloudError} When the API returns an error
1074
+ *
1075
+ * @example
1076
+ * ```ts
1077
+ * const tracks = await sc.tracks.getTracks([123456, 234567, 345678]);
1078
+ * tracks.forEach(t => console.log(t.title));
1079
+ * ```
1080
+ *
1081
+ * @see https://developers.soundcloud.com/docs/api/explorer/open-api#/tracks/get_tracks
1082
+ */
1083
+ async getTracks(ids, options) {
1084
+ const t = resolveToken(this.getToken, options?.token);
1085
+ return this.fetch({ path: `/tracks?ids=${ids.join(",")}`, method: "GET", token: t });
1086
+ }
1046
1087
  /**
1047
1088
  * Get stream URLs for a track.
1048
1089
  *
@@ -1639,6 +1680,7 @@ var IMPLEMENTED_OPERATIONS = [
1639
1680
  "get_me_followers",
1640
1681
  "get_me_playlists",
1641
1682
  "get_me_tracks",
1683
+ "get_me_connections",
1642
1684
  // Users
1643
1685
  "get_users_user_id",
1644
1686
  "get_users_user_id_followers",
@@ -1800,6 +1842,13 @@ var getUserWebProfiles = (token, userId) => scFetch({ path: `/users/${userId}/we
1800
1842
  // src/tracks/getTrack.ts
1801
1843
  var getTrack = (token, trackId) => scFetch({ path: `/tracks/${trackId}`, method: "GET", token });
1802
1844
 
1845
+ // src/tracks/getTracks.ts
1846
+ var getTracks = (token, ids) => scFetch({
1847
+ path: `/tracks?ids=${ids.join(",")}`,
1848
+ method: "GET",
1849
+ token
1850
+ });
1851
+
1803
1852
  // src/tracks/getComments.ts
1804
1853
  var getTrackComments = (token, trackId, limit) => scFetch({ path: `/tracks/${trackId}/comments?threaded=1&filter_replies=0${limit ? `&limit=${limit}` : ""}&linked_partitioning=true`, method: "GET", token });
1805
1854
 
@@ -1918,6 +1967,9 @@ var getMePlaylists = (token, limit) => scFetch({ path: `/me/playlists?${limit ?
1918
1967
  // src/me/tracks.ts
1919
1968
  var getMeTracks = (token, limit) => scFetch({ path: `/me/tracks?${limit ? `limit=${limit}&` : ""}linked_partitioning=true`, method: "GET", token });
1920
1969
 
1970
+ // src/me/connections.ts
1971
+ var getMeConnections = (token) => scFetch({ path: "/me/connections", method: "GET", token });
1972
+
1921
1973
  // src/likes/index.ts
1922
1974
  var likePlaylist = async (token, playlistId) => {
1923
1975
  try {
@@ -1973,6 +2025,6 @@ var unrepostPlaylist = async (token, playlistId) => {
1973
2025
  // src/utils/widget.ts
1974
2026
  var getSoundCloudWidgetUrl = (trackId) => `https%3A//api.soundcloud.com/tracks/${trackId}&show_teaser=false&color=%2300a99d&inverse=false&show_user=false&sharing=false&buying=false&liking=false&show_artwork=false&show_name=false`;
1975
2027
 
1976
- export { IMPLEMENTED_OPERATIONS, InFlightDeduper, RawClient, SoundCloudClient, createPlaylist, createTrackComment, deletePlaylist, deleteTrack, fetchAll, followUser, generateCodeChallenge, generateCodeVerifier, getAuthorizationUrl, getClientToken, getFollowers, getFollowings, getMe, getMeActivities, getMeActivitiesOwn, getMeActivitiesTracks, getMeFollowers, getMeFollowings, getMeFollowingsTracks, getMeLikesPlaylists, getMeLikesTracks, getMePlaylists, getMeTracks, getPlaylist, getPlaylistReposts, getPlaylistTracks, getRelatedTracks, getSoundCloudWidgetUrl, getTrack, getTrackComments, getTrackLikes, getTrackReposts, getTrackStreams, getUser, getUserLikesPlaylists, getUserLikesTracks, getUserPlaylists, getUserToken, getUserTracks, getUserWebProfiles, likePlaylist, likeTrack, paginate, paginateItems, refreshUserToken, repostPlaylist, repostTrack, resolveUrl, scFetch, scFetchUrl, searchPlaylists, searchTracks, searchUsers, signOut, unfollowUser, unlikePlaylist, unlikeTrack, unrepostPlaylist, unrepostTrack, updatePlaylist, updateTrack };
1977
- //# sourceMappingURL=chunk-JLRQJWU5.mjs.map
1978
- //# sourceMappingURL=chunk-JLRQJWU5.mjs.map
2028
+ export { IMPLEMENTED_OPERATIONS, InFlightDeduper, RawClient, SoundCloudClient, createPlaylist, createTrackComment, deletePlaylist, deleteTrack, fetchAll, followUser, generateCodeChallenge, generateCodeVerifier, getAuthorizationUrl, getClientToken, getFollowers, getFollowings, getMe, getMeActivities, getMeActivitiesOwn, getMeActivitiesTracks, getMeConnections, getMeFollowers, getMeFollowings, getMeFollowingsTracks, getMeLikesPlaylists, getMeLikesTracks, getMePlaylists, getMeTracks, getPlaylist, getPlaylistReposts, getPlaylistTracks, getRelatedTracks, getSoundCloudWidgetUrl, getTrack, getTrackComments, getTrackLikes, getTrackReposts, getTrackStreams, getTracks, getUser, getUserLikesPlaylists, getUserLikesTracks, getUserPlaylists, getUserToken, getUserTracks, getUserWebProfiles, likePlaylist, likeTrack, paginate, paginateItems, refreshUserToken, repostPlaylist, repostTrack, resolveUrl, scFetch, scFetchUrl, searchPlaylists, searchTracks, searchUsers, signOut, unfollowUser, unlikePlaylist, unlikeTrack, unrepostPlaylist, unrepostTrack, updatePlaylist, updateTrack };
2029
+ //# sourceMappingURL=chunk-JH7TLL2C.mjs.map
2030
+ //# sourceMappingURL=chunk-JH7TLL2C.mjs.map