npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zibot/scdl

v0.0.6

Published

Soucloud download

Readme

@zibot/scdl

A tiny, promise-based Node.js client for searching SoundCloud, fetching track/playlist details, and downloading tracks as readable streams.

  • Search tracks, playlists, and users
  • Inspect rich metadata for tracks and playlists
  • Download a track stream (high/low quality)
  • Discover related tracks by URL or ID

⚠️ Respect SoundCloud’s Terms of Service and creator rights. This library is for personal/educational use; don’t redistribute copyrighted content without permission.


Installation

npm i @zibot/scdl
# or
yarn add @zibot/scdl
# or
pnpm add @zibot/scdl

Runtime: Node.js 18+ is recommended (built-in fetch/WHATWG streams). Works with ESM or CommonJS.


Quick Start

ESM

import SoundCloud from "@zibot/scdl";

const sc = new SoundCloud({ init: true }); // auto-initialize clientId

(async () => {
  // Search tracks
  const results = await sc.searchTracks({ query: "lofi hip hop", limit: 5, type: "tracks" });
  console.log(results);

  // Track details
  const track = await sc.getTrackDetails("https://soundcloud.com/user/track-slug");
  console.log(track.title, track.user.username);

  // Download a track (Readable stream)
  const stream = await sc.downloadTrack("https://soundcloud.com/user/track-slug", { quality: "high" });
  stream.pipe(process.stdout); // or pipe to fs.createWriteStream("track.mp3")

  // Related tracks
  const related = await sc.getRelatedTracks(track.id, { limit: 10 });
  console.log(related.map(t => t.title));
})();

CommonJS

const SoundCloud = require("@zibot/scdl");
const fs = require("node:fs");

const sc = new SoundCloud({ init: true });

(async () => {
  const stream = await sc.downloadTrack("https://soundcloud.com/user/track-slug");
  stream.pipe(fs.createWriteStream("track.mp3"));
})();

Initialization

The client can retrieve a valid clientId automatically.

// Option A: auto-init (recommended)
const sc = new SoundCloud({ init: true });

// Option B: manual init
const sc = new SoundCloud();
await sc.init(); // retrieves clientId

You usually only need to call init() once per process. If you get authentication errors later, re-calling init() may refresh the client ID.


API

new SoundCloud(options?)

Create a client.

  • options.init?: boolean – if true, calls init() internally.

Properties

  • clientId: string | null – resolved after init()
  • apiBaseUrl: string – internal base URL used for API calls

init(): Promise<void>

Initialize the client (retrieve clientId). Call this if you didn’t pass { init: true }.


searchTracks(options: SearchOptions): Promise<(Track | Playlist | User)[]>

Search SoundCloud.

Parameters – SearchOptions

  • query: string – search text
  • limit?: number – default depends on endpoint (commonly 10–20)
  • offset?: number – for pagination
  • type?: "all" | "tracks" | "playlists" | "users" – filter result kinds (default "all")

Usage

// Top tracks
const tracks = await sc.searchTracks({ query: "ambient study", type: "tracks", limit: 10 });

// Mixed kinds (tracks/playlists/users)
const mixed = await sc.searchTracks({ query: "chill", type: "all", limit: 5, offset: 5 });

getTrackDetails(url: string): Promise<Track>

Get rich metadata for a single track by its public URL.

const t = await sc.getTrackDetails("https://soundcloud.com/artist/track");
console.log({
  id: t.id,
  title: t.title,
  by: t.user.username,
  streamables: t.media.transcodings.length,
});

Track

interface Track {
  id: number;
  title: string;
  url: string;
  user: { id: number; username: string };
  media: {
    transcodings: {
      url: string;
      format: { protocol: string; mime_type: string };
    }[];
  };
}

getPlaylistDetails(url: string): Promise<Playlist>

Fetch playlist metadata and contained tracks.

const pl = await sc.getPlaylistDetails("https://soundcloud.com/artist/sets/playlist-slug");
console.log(pl.title, pl.tracks.length);

Playlist

interface Playlist {
  id: number;
  title: string;
  tracks: Track[];
}

downloadTrack(url: string, options?: DownloadOptions): Promise<Readable>

Download a track as a Node Readable stream.

Parameters – DownloadOptions

  • quality?: "high" | "low" – choose available transcoding (default implementation prefers higher quality when available)

Examples

import fs from "node:fs";

const read = await sc.downloadTrack("https://soundcloud.com/user/track", { quality: "high" });
await new Promise((resolve, reject) => {
  read.pipe(fs.createWriteStream("track.mp3"))
    .on("finish", resolve)
    .on("error", reject);
});

Pipe to any writable destination (stdout, HTTP response, cloud storage SDKs, etc.).


getRelatedTracks(track: string | number, opts?: { limit?: number; offset?: number }): Promise<Track[]>

Fetch tracks related to a given track by URL or numeric ID.

// by URL
const relByUrl = await sc.getRelatedTracks("https://soundcloud.com/artist/track", { limit: 6 });

// by ID
const base = await sc.getTrackDetails("https://soundcloud.com/artist/track");
const relById = await sc.getRelatedTracks(base.id, { limit: 6 });

Types

export interface SearchOptions {
  query: string;
  limit?: number;
  offset?: number;
  type?: "all" | "tracks" | "playlists" | "users";
}

export interface DownloadOptions {
  quality?: "high" | "low";
}

export interface User {
  id: number;
  username: string;
  followers_count: number;
  track_count: number;
}

These types are exported for TypeScript consumers.


Examples

Save the highest-quality stream to disk

import fs from "node:fs";
import SoundCloud from "@zibot/scdl";

const sc = new SoundCloud({ init: true });

async function save(url: string, file: string) {
  const stream = await sc.downloadTrack(url, { quality: "high" });
  await new Promise<void>((resolve, reject) => {
    stream.pipe(fs.createWriteStream(file))
      .on("finish", resolve)
      .on("error", reject);
  });
}

save("https://soundcloud.com/user/track", "output.mp3");

Basic search → pick first result → download

const [first] = await sc.searchTracks({ query: "deep house 2024", type: "tracks", limit: 1 });
if (first && "url" in first) {
  const s = await sc.downloadTrack(first.url);
  s.pipe(process.stdout);
}

Paginate results

const page1 = await sc.searchTracks({ query: "vaporwave", type: "tracks", limit: 20, offset: 0 });
const page2 = await sc.searchTracks({ query: "vaporwave", type: "tracks", limit: 20, offset: 20 });

Error Handling & Tips

  • Initialization: If a method throws due to missing/expired clientId, call await sc.init() and retry.
  • Quality selection: Not all tracks expose multiple transcodings; the library will fall back when needed.
  • Rate limits / 429: Back off and retry with an exponential strategy.
  • Private/geo-restricted tracks: Details/downloads may be unavailable.
  • Networking: Wrap downloads with proper error and close handlers to avoid dangling file descriptors.

FAQ

Q: Can I use this in the browser? This package targets Node.js (it returns Node Readable). Browser use is not supported.

Q: What audio format do I get? Whatever the selected transcoding provides (commonly progressive MP3 or HLS AAC). You may need to remux/encode if you require a specific container/codec.

Q: Do I need my own client ID? The client auto-discovers a valid clientId. If discovery fails due to upstream changes, update the package to the latest version.


Contributing

PRs and issues are welcome! Please include:

  • A clear description of the change
  • Repro steps (for bugs)
  • Tests where possible

License

MIT © Zibot


Disclaimer

This project is not affiliated with SoundCloud. Use responsibly and comply with all applicable laws and SoundCloud’s Terms of Service.