This document provides comprehensive documentation for Beatport's APIs, discovered through frontend analysis and reverse engineering.
Beatport uses two main API infrastructures:
| API | Base URL | Purpose | Authentication |
|---|---|---|---|
| Internal API | https://api-internal.beatportprod.com/v4 |
Server-side rendering, frontend data | Server-side only |
| Public API | https://api.beatport.com/v4 |
OAuth-based access for third-party apps | OAuth 2.0 (PKCE) |
The internal API (api-internal.beatportprod.com) is what Beatport's Next.js frontend uses server-side to fetch data during SSR. The public API (api.beatport.com) provides OAuth-authenticated access for external applications.
This API is used by Beatport's server-side rendering. Requests are made from Beatport's servers, not from the browser.
https://api-internal.beatportprod.com/v4
| Endpoint | Description |
|---|---|
GET /catalog/tracks/{id}/ |
Get track details by ID |
GET /catalog/tracks/ |
List tracks with pagination |
GET /catalog/tracks/top/{genre_id}/?per_page=100&page=1 |
Get top tracks for a genre |
Track Response Fields:
{
"id": 23011269,
"name": "Greece 2000",
"mix_name": "Max Styler Extended Rework",
"slug": "greece-2000",
"artists": [
{"id": 8783, "name": "Three Drives", "slug": "three-drives", "url": "/artist/three-drives/8783", "image": {...}}
],
"remixers": [],
"release": {
"id": 5707323,
"name": "Greece 2000 - Max Styler Rework",
"slug": "greece-2000-max-styler-rework",
"label": {"id": 33099, "name": "Armada Music", "slug": "armada-music"}
},
"genre": {"id": 7, "name": "Trance (Main Floor)", "slug": "trance-main-floor"},
"sub_genre": {"id": 27, "name": "...", "slug": "..."},
"bpm": 128,
"key": {"id": 16, "name": "A min", "camelot": "8A"},
"length": "6:45",
"length_ms": 405000,
"price": {"code": "USD", "symbol": "$", "value": 1.69},
"publish_date": "2024-01-15",
"new_release_date": "2024-01-15",
"sample_url": "https://geo-samples.beatport.com/track/{uuid}.LOFI.mp3",
"sample_start_ms": 130976,
"sample_end_ms": 250976,
"image": {
"id": "...",
"uri": "https://geo-media.beatport.com/image_size/590x404/{uuid}.jpg",
"dynamic_uri": "https://geo-media.beatport.com/image_size/{w}x{h}/{uuid}.jpg"
},
"isrc": "...",
"exclusive": false,
"is_available_for_streaming": true,
"is_explicit": false,
"is_hype": false,
"is_classic": false,
"current_status": {"id": 8, "name": "Published"},
"sale_type": {"id": 1, "name": "..."}
}| Endpoint | Description |
|---|---|
GET /catalog/artists/{id}/ |
Get artist details |
GET /catalog/artists/{id}/top-10-tracks/?per_page=10 |
Get artist's top tracks |
Artist Response Fields:
{
"id": 26182,
"name": "deadmau5",
"slug": "deadmau5",
"bio": "...",
"website": "https://...",
"image": {...},
"dj_association": {...},
"enabled": true
}| Endpoint | Description |
|---|---|
GET /catalog/labels/{id}/ |
Get label details |
GET /catalog/labels/{id}/top-10-tracks/?per_page=10 |
Get label's top tracks |
Label Response Fields:
{
"id": 2027,
"name": "Drumcode",
"slug": "drumcode",
"bio": "...",
"image": {...},
"enabled": true,
"has_exclusive_contract": false,
"is_available_for_streaming": true,
"is_available_for_bundle_subscription": true,
"is_available_for_hype": true,
"latest_active_publish_date": "2024-01-20"
}| Endpoint | Description |
|---|---|
GET /catalog/releases/{id}/ |
Get release details |
Release Response Fields:
{
"id": 5691573,
"name": "Misbehave",
"slug": "misbehave",
"artists": [...],
"label": {...},
"catalog_number": "...",
"upc": "...",
"type": "EP",
"desc": "...",
"bpm_range": {"min": 120, "max": 128},
"tracks": [...],
"track_count": 4,
"price": {...},
"publish_date": "2024-01-15",
"image": {...},
"exclusive": false,
"is_available_for_streaming": true,
"is_hype": false
}| Endpoint | Description |
|---|---|
GET /catalog/genres/ |
List all genres |
GET /catalog/genres/{id}/ |
Get genre details |
GET /catalog/genres/{id}/top-10-tracks/?per_page=10 |
Get genre's top tracks |
GET /catalog/genres/{id}/top-10-releases |
Get genre's top releases |
Known Genre IDs:
| ID | Name |
|---|---|
| 1 | Drum & Bass |
| 3 | Electronica |
| 5 | House |
| 6 | Techno (Peak Time / Driving) |
| 7 | Trance (Main Floor) |
| 8 | Hard Dance / Hardcore / Neo Rave |
| 9 | Breaks / Breakbeat / UK Bass |
| 11 | Tech House |
| 12 | Deep House |
| 13 | Psy-Trance |
| 14 | Minimal / Deep Tech |
| 15 | Progressive House |
| 18 | Dubstep |
| 37 | Indie Dance |
| 38 | Trap / Future Bass |
| 39 | Dance / Pop |
| 50 | Nu Disco / Disco |
| 86 | UK Garage / Bassline |
| 89 | Afro House |
| 90 | Melodic House & Techno |
| 91 | Bass House |
| 92 | Techno (Raw / Deep / Hypnotic) |
| 96 | Mainstage |
Genre Response Fields:
{
"id": 6,
"name": "Techno (Peak Time / Driving)",
"slug": "techno-peak-time-driving",
"enabled": true,
"is_included_in_hype": true,
"sub_genres": [...]
}| Endpoint | Description |
|---|---|
GET /catalog/sub-genres/{id}/ |
Get sub-genre details |
| Endpoint | Description |
|---|---|
GET /catalog/charts/ |
List charts |
GET /catalog/charts/{id}/ |
Get chart details |
| Endpoint | Description |
|---|---|
GET /catalog/keys/{id}/ |
Get key details |
| Endpoint | Description |
|---|---|
GET /catalog/chord-types/{id}/ |
Get chord type |
| Endpoint | Description |
|---|---|
GET /catalog/search/?q={query}&type={type} |
Search catalog |
Search Types: tracks, artists, labels, releases, charts
Search Response:
{
"tracks": {
"data": [
{
"score": 100.5,
"artist_id": 26182,
"artist_name": "deadmau5",
"genre": [{"genre_id": 1, "genre_name": "..."}],
"artist_image_uri": "...",
"artist_image_dynamic_uri": "..."
}
]
},
"artists": {...},
"labels": {...},
"releases": {...},
"charts": {...}
}| Endpoint | Description |
|---|---|
GET /auxiliary/current-status/{id}/ |
Track status (e.g., Published) |
GET /auxiliary/sale-types/{id}/ |
Sale type info |
GET /auxiliary/exclusive-period/{id}/ |
Exclusive period info |
GET /auxiliary/item-types/ |
List item types |
GET /auxiliary/page-module-types/ |
Page module types |
GET /auxiliary/page-types/ |
Page types |
GET /auxiliary/source-types/ |
Source types |
| Endpoint | Description |
|---|---|
GET /curation/pages/ |
Curated pages |
GET /curation/page-module-styles/ |
Page module styles |
All list endpoints use consistent pagination:
{
"next": "https://api-internal.beatportprod.com/v4/...?page=2",
"previous": null,
"count": 1000,
"page": 1,
"per_page": 25,
"results": [...]
}Query Parameters:
page- Page number (default: 1)per_page- Results per page (default: 25, max: 100)
The public API requires OAuth 2.0 authentication and provides access to user-specific data.
https://api.beatport.com/v4
- Get Client ID - Scraped from
https://api.beatport.com/v4/docs/JavaScript files - Authorization URL:
GET /auth/o/authorize
?client_id={client_id}
&response_type=code
&redirect_uri={redirect_uri}
&scope=library
&code_challenge={code_challenge}
&code_challenge_method=S256
- Token Exchange:
POST /auth/o/token/
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code={authorization_code}
&redirect_uri={redirect_uri}
&client_id={client_id}
&code_verifier={code_verifier}
- Token Refresh:
POST /auth/o/token/
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token={refresh_token}
&client_id={client_id}
Token Response:
{
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "library"
}POST /auth/login/
Content-Type: application/json
{
"username": "user@example.com",
"password": "password"
}
Returns session cookies for web scraping.
Authorization: Bearer {access_token}
Accept: application/json
| Endpoint | Description |
|---|---|
GET /my/account/ |
Get current user profile |
Response:
{
"id": 12345,
"username": "user",
"email": "user@example.com",
"first_name": "...",
"last_name": "...",
"created": "...",
"modified": "..."
}| Endpoint | Description |
|---|---|
GET /my/beatport/tracks/?page=1&per_page=100 |
Get user's saved tracks |
Same as internal API, but authenticated:
| Endpoint | Description |
|---|---|
GET /catalog/tracks/{id}/ |
Get track details |
GET /catalog/tracks/?page=1&per_page=25&order_by=-publish_date |
List tracks |
GET /catalog/search/?q={query}&type=tracks |
Search tracks |
order_by=publish_date- Oldest firstorder_by=-publish_date- Newest first (descending)
https://geo-samples.beatport.com/track/{uuid}.LOFI.mp3
Sample playback is controlled by sample_start_ms and sample_end_ms fields (typically 2-minute preview window).
Static size:
https://geo-media.beatport.com/image_size/590x404/{uuid}.jpg
Dynamic size:
https://geo-media.beatport.com/image_size/{width}x{height}/{uuid}.jpg
Common sizes: 100x100, 250x250, 500x500, 590x404, 1400x1400
Beatport uses Next.js with Server-Side Rendering (SSR). Key observations:
-
Data Fetching: All API calls happen server-side during SSR
-
State Management: Uses React Query with dehydrated state
-
Query Keys: Follow patterns like:
track-{id}release-{id}artist-{id}label-{id}genre-{id}search-alltop-10-tracks-per_page=10page-module-{id}-items
-
Data available in
window.__NEXT_DATA__:
{
"buildId": "...",
"page": "/track/[slug]/[id]",
"props": {
"pageProps": {
"dehydratedState": {
"queries": [
{
"queryKey": ["track-23011269"],
"state": { "data": {...} }
}
]
}
}
}
}No official rate limits documented, but recommended practices:
- Add 500ms delay between requests when scraping
- Use pagination instead of large
per_pagevalues - Cache responses where appropriate
{
"detail": "Not found.",
"error": "...",
"status_code": 404
}Common HTTP status codes:
200- Success302- Redirect (often to login for auth-required endpoints)400- Bad request401- Unauthorized403- Forbidden404- Not found429- Rate limited
$response = $httpClient->request('GET', 'https://api.beatport.com/v4/catalog/tracks/23011269/', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Accept' => 'application/json',
],
]);
$track = $response->toArray();$response = $httpClient->request('GET', 'https://api.beatport.com/v4/catalog/search/', [
'query' => [
'q' => 'deadmau5',
'type' => 'tracks',
'page' => 1,
'per_page' => 25,
],
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);$tracks = [];
$page = 1;
do {
$response = $httpClient->request('GET', 'https://api.beatport.com/v4/my/beatport/tracks/', [
'query' => ['page' => $page, 'per_page' => 100],
'headers' => ['Authorization' => 'Bearer ' . $accessToken],
]);
$data = $response->toArray();
$tracks = array_merge($tracks, $data['results']);
$hasMore = $data['next'] !== null;
$page++;
} while ($hasMore);- 2026-02-05: Initial documentation created from frontend analysis