Beatport Download via Browser Tool

MCP Tools

Download purchased tracks from Beatport using the openclaw headless browser tool (CDP). Handles login, authentication via NextAuth, enabling downloads in headless Chrome, and saving files locally. Use when the user asks to download music, tracks, or files from Beatport, or manage their Beatport purchases/library. Triggers on phrases like "download from beatport", "beatport download", "download my tracks", "get my beatport music".

Install

openclaw skills install beatport-dl-with-browser-tool

Beatport Download via Browser Tool

Download purchased Beatport tracks through the openclaw headless browser using CDP (Chrome DevTools Protocol).

Prerequisites

  • openclaw browser running on 127.0.0.1:9222
  • Beatport credentials (username + password)
  • ws module at /opt/homebrew/lib/node_modules/openclaw/node_modules/ws
  • Node.js runtime

Authentication Flow

Beatport uses a dual-auth system:

  1. account.beatport.com — Django session (sessionid cookie)
  2. www.beatport.com — NextAuth (__Secure-next-auth.session-token cookie)

Login Steps

  1. Navigate to https://account.beatport.com/ via CDP Page.navigate
  2. Fill username/password via Runtime.evaluate (use native input setters to bypass React controlled inputs)
  3. Submit the login form
  4. On the www.beatport.com tab, sign in via NextAuth:
// In browser context on www.beatport.com
fetch("/api/auth/csrf").then(r => r.json()).then(csrf => {
  const fd = new URLSearchParams();
  fd.append("csrfToken", csrf.csrfToken);
  fd.append("username", "USER");
  fd.append("password", "PASS");
  fd.append("callbackUrl", "https://www.beatport.com/");
  // Create hidden form and submit (fetch redirect fails cross-origin)
  const form = document.createElement("form");
  form.method = "POST";
  form.action = "/api/auth/signin/beatport";
  form.style.display = "none";
  for (const [k, v] of Object.entries(Object.fromEntries(fd))) {
    const inp = document.createElement("input");
    inp.type = "hidden"; inp.name = k; inp.value = v;
    form.appendChild(inp);
  }
  document.body.appendChild(form);
  form.submit();
});
  1. Verify login: Account menu button should appear in navbar (no Create Account or Log In button)

Key URLs

PageURLPurpose
Carthttps://www.beatport.com/cartItems pending purchase
Libraryhttps://www.beatport.com/libraryPurchased tracks (may show Upgrade for free accounts)
Downloadshttps://www.beatport.com/library/downloadsDownload queue
Checkouthttps://www.beatport.com/checkoutPayment page

Note: /my-beatport/downloads and /my-beatport/collection return 404. The correct paths are /library and /library/downloads.

Enabling Downloads in Headless Chrome

Headless Chrome cancels downloads by default. Enable via CDP on the browser-level WebSocket:

// Browser-level WS: ws://127.0.0.1:9222/devtools/browser/<id>
ws.send(JSON.stringify({
  id: 1,
  method: "Browser.setDownloadBehavior",
  params: {
    behavior: "allowAndName",
    downloadPath: "/path/to/download/dir/",
    eventsEnabled: true
  }
}));

Get browser ID from http://127.0.0.1:9222/json/versionwebSocketDebuggerUrl.

Downloading Tracks

Step 1: Add tracks to download queue

On /library, each track has a re-download icon (svg[data-testid='icon-re-download']). Click each one to add to the download queue:

var icons = document.querySelectorAll("svg[data-testid='icon-re-download']");
icons.forEach(function(icon, i) {
  setTimeout(function() { icon.closest("button, div").click(); }, i * 500);
});

Step 2: Download from queue page

Navigate to /library/downloads. All queued tracks appear with a "Download All" button.

Step 3: Click Download All

Enable browser downloads first (see above), then click:

var btn = [...document.querySelectorAll("button")].find(b => b.innerText.includes("Download All"));
if (btn) btn.click();

The download arrives as a zip file (e.g. beatport_tracks_2026-04.zip).

Step 4: Unzip and clean up

cd /path/to/download/dir
unzip -o beatport_tracks_*.zip -d tmp/
mv tmp/*.mp3 .
rm -rf tmp/ beatport_tracks_*.zip

Download URL Format

https://zips.beatport.com/v1/download?token=<JWT_TOKEN>

The token is single-use and expires quickly. Always capture fresh from events.

Download URL Format

https://zips.beatport.com/v1/download?token=<JWT_TOKEN>

The token is single-use and expires quickly. Always capture it fresh from the Page.downloadWillBegin event.

API Access

Access Token

curl -s -H "Cookie: <cookies>" \
  "https://www.beatport.com/_next/data/<buildId>/en/library/downloads.json" \
  | jq -r '.pageProps.accessToken'

Library Data

curl -s -H "Cookie: <cookies>" \
  "https://www.beatport.com/_next/data/<buildId>/en/library.json" \
  | jq '.pageProps.dehydratedState.queries[].state.data.results[] | {name, id, artists}'

Build ID

curl -s "https://www.beatport.com/" | grep -o '"buildId":"[^"]*"' | head -1

Current buildId (subject to change): PWoDyRo_P5V8lNYu_92bX

Common Pitfalls

  1. Cross-domain navigation fails with Page.navigate — Use location.href = "..." via Runtime.evaluate instead
  2. React controlled inputs don't respond to .value = — Use native input value setter:
    var input = document.querySelector("input[name=username]");
    var nativeSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
    nativeSetter.call(input, "username");
    input.dispatchEvent(new Event("input", { bubbles: true }));
    
  3. Node.js string escaping in -e — Use String.raw\...`template literals, or write code to a file and run withnode file.js`
  4. Free account download limit — 20 downloads per track. "Unlimited re-downloads" requires Beatport Streaming subscription
  5. CDP exec timeout — openclaw kills long-running node processes (~10s). Keep CDP operations short; use background: true + process poll for longer waits
  6. curl path — Use /usr/bin/curl, not /opt/homebrew/bin/curl (may not exist)

CDP Helper Pattern

Write scripts to files to avoid shell escaping issues:

// scripts/beatport-cdp.js
const WS = require("/opt/homebrew/lib/node_modules/openclaw/node_modules/ws");
const http = require("http");

function getPage(filter) {
  return new Promise((resolve) => {
    http.get("http://127.0.0.1:9222/json", (res) => {
      let body = "";
      res.on("data", (c) => body += c);
      res.on("end", () => {
        const pages = JSON.parse(body).filter(p => p.type === "page");
        resolve(filter ? pages.find(filter) || pages[0] : pages[0]);
      });
    });
  });
}

function cdpEval(ws, expression) {
  return new Promise((resolve) => {
    ws.send(JSON.stringify({ id: Date.now(), method: "Runtime.evaluate", params: { expression, returnByValue: true } }));
    ws.on("message", (m) => {
      const d = JSON.parse(m.toString());
      if (d.id && d.result) { resolve(d.result); }
    });
  });
}

async function screenshot(ws, path) {
  return new Promise((resolve) => {
    ws.send(JSON.stringify({ id: Date.now(), method: "Page.captureScreenshot", params: { format: "png" } }));
    ws.on("message", (m) => {
      const d = JSON.parse(m.toString());
      if (d.id && d.result && d.result.data) {
        require("fs").writeFileSync(path, Buffer.from(d.result.data, "base64"));
        resolve();
      }
    });
  });
}

module.exports = { getPage, cdpEval, screenshot };

Format Compatibility

  • CDJ-2000: MP3 or WAV
  • Beatport download options: MP3, WAV, AIFF, FLAC
  • Default is MP3; select WAV/AIFF on cart page or account settings if needed for CDJ compatibility