Spotify Playlist Search API. Playlists matching your search.
Playlists and their curators by keyword. We handle the proxies, blocks and platform changes.
Spotify Playlist Search API at a glance
Searches Spotify's public playlists by keyword in any language and returns them a page at a time, in Spotify's order. Each has its title, description, cover, owner with their username and picture, the kind of list Spotify says it is, and whether its algorithm made it. The Spotify Playlist API adds the follower count, item count and dates.
GET /v1/spotify/search_playlists
- 1 credit per page, $0.75–$1.97 per 1,000 pages
- 13 typed fields null when unknown
- 100 playlists per page, Spotify's own ranking for the query
queryis all it needs, e.g.workout
Send one request. Get up to 100 playlists back.
Copy the request in your language, or hand it to your agent. Sign up for an API key and your first 100 credits are free.
$ npm install socialtapquery = workoutconst params = new URLSearchParams({
query: "workout",
});
const res = await fetch(`https://socialtap.dev/v1/spotify/search_playlists?${params}`, {
headers: { "x-api-key": process.env.SOCIALTAP_API_KEY ?? "" },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
const { data, page, meta } = body;
console.log(data, meta.credits_charged);import os
import requests
res = requests.get(
"https://socialtap.dev/v1/spotify/search_playlists",
params={"query": "workout"},
headers={"x-api-key": os.environ["SOCIALTAP_API_KEY"]},
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
data, page, meta = body["data"], body["page"], body["meta"]
print(data, meta["credits_charged"])require "json"
require "net/http"
uri = URI("https://socialtap.dev/v1/spotify/search_playlists")
uri.query = URI.encode_www_form('query' => 'workout')
req = Net::HTTP::Get.new(uri)
req["x-api-key"] = ENV.fetch("SOCIALTAP_API_KEY")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
body = JSON.parse(res.body)
raise "#{body["error"]["code"]}: #{body["error"]["message"]}" unless res.is_a?(Net::HTTPSuccess)
pp body["data"]
puts body["meta"]["credits_charged"]<?php
$query = http_build_query([
'query' => 'workout',
]);
$ch = curl_init('https://socialtap.dev/v1/spotify/search_playlists?' . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('SOCIALTAP_API_KEY')],
]);
$body = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
throw new RuntimeException($body['error']['code'] . ': ' . $body['error']['message']);
}
print_r($body['data']);
echo $body['meta']['credits_charged'], PHP_EOL;package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
)
func main() {
q := url.Values{}
q.Set("query", "workout")
req, err := http.NewRequest("GET", "https://socialtap.dev/v1/spotify/search_playlists?"+q.Encode(), nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("x-api-key", os.Getenv("SOCIALTAP_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var body struct {
Data json.RawMessage `json:"data"`
Page json.RawMessage `json:"page"`
Meta map[string]any `json:"meta"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
log.Fatal(err)
}
if body.Error != nil {
log.Fatalf("%s: %s", body.Error.Code, body.Error.Message)
}
fmt.Println(string(body.Data), body.Meta["credits_charged"])
}curl 'https://socialtap.dev/v1/spotify/search_playlists?query=workout' \
-H "x-api-key: $SOCIALTAP_API_KEY"{
"data": [
{
"approximate_fields": [],
"description": "Bangers Workout music for you. Also try rap, latin, reggaeton, dance, exotica",
"id": "37i9dQZF1EIeNbDmYrgnbS",
"is_collaborative": null,
"metrics": {
"post_count": null
},
"owner": {
"avatar_url": "https://i.scdn.co/image/ab6775700000ee8555c25988a6ac314394d3fbf5",
"display_name": "Spotify",
"id": null,
"is_verified": null,
"url": "https://open.spotify.com/user/spotify",
"username": "spotify"
},
"platform": "spotify",
"platform_specific": {
"spotify": {
"created_at": null,
"follower_count": null,
"is_algorithmic": null,
"is_collaborative": null,
"is_deleted_by_owner": null,
"is_user_created": null,
"updated_at": null
}
},
"platform_type": "descripto",
"thumbnail_url": "https://seed-mix-image.spotifycdn.com/v6/img/desc/Bangers%20Workout/en/default",
"title": "Bangers Workout Mix",
"type": "playlist",
"unknown_fields": [],
"url": "https://open.spotify.com/playlist/37i9dQZF1EIeNbDmYrgnbS"
},
{
"approximate_fields": [],
"description": null,
"id": "2rKxqZEf3zcoateybTiJDn",
"is_collaborative": null,
"metrics": {
"post_count": null
},
"owner": {
"avatar_url": "https://i.scdn.co/image/ab6775700000ee859e5e623e05ef20c32dc9d8bc",
"display_name": "PureWow",
"id": null,
"is_verified": null,
"url": "https://open.spotify.com/user/purewow",
"username": "purewow"
},
"platform": "spotify",
"platform_specific": {
"spotify": {
"created_at": null,
"follower_count": null,
"is_algorithmic": null,
"is_collaborative": null,
"is_deleted_by_owner": null,
"is_user_created": null,
"updated_at": null
}
},
"platform_type": "playlist",
"thumbnail_url": "https://mosaic.scdn.co/640/ab67616d00001e0282db43e5091301205076d6abab67616d00001e02926f43e7cce571e62720fd46ab67616d00001e02b52264e58d9bb36257770a89ab67616d00001e02f9f27162ab1ed45b8d7a7e98",
"title": "50 Best Workout Songs That Will Have You *Actually* Breaking a Sweat",
"type": "playlist",
"unknown_fields": [],
"url": "https://open.spotify.com/playlist/2rKxqZEf3zcoateybTiJDn"
},
{
"approximate_fields": [],
"description": "LOCK IN LADIES!!! 💪🏼💸💖🧘🏻♀️ Revenge Body So He Regrets It, Hot Girl Walk, Hot Girls Lift Weights, Fitness Girlies, Yoga, Running, Pilates, Treadmill, Cardio, Bad Bitch Workout Playlist, Hot Gym Girls, Hot Girl Workout, BEST GYM SONGS HYPE",
"id": "0OxRbscW9gq3FzENFlUPes",
"is_collaborative": null,
"metrics": {
"post_count": null
},
"owner": {
"avatar_url": "https://i.scdn.co/image/ab6775700000ee850afa00fab7ae8a4c76278e1a",
"display_name": "BAD KITTY 😈",
"id": null,
"is_verified": null,
"url": "https://open.spotify.com/user/lqz5moj5fvn3k4mrdg9ewu0g1",
"username": "lqz5moj5fvn3k4mrdg9ewu0g1"
},
"platform": "spotify",
"platform_specific": {
"spotify": {
"created_at": null,
"follower_count": null,
"is_algorithmic": null,
"is_collaborative": null,
"is_deleted_by_owner": null,
"is_user_created": null,
"updated_at": null
}
},
"platform_type": "playlist",
"thumbnail_url": "https://image-cdn-fa.spotifycdn.com/image/ab67706c0000da847168a8140b4499c5b1322dbd",
"title": "HOT GIRL GYM PLAYLIST 🎀🏋🏻♀️ Girly Gym Pump, Workout Songs 2026, Beast Mode Baddie Gym Music",
"type": "playlist",
"unknown_fields": [],
"url": "https://open.spotify.com/playlist/0OxRbscW9gq3FzENFlUPes"
}
],
"page": {
"has_more": true,
"next_cursor": "c1.EXAMPLE_CURSOR",
"next_cursor_expires_at": "2026-10-26T12:00:00Z",
"end_reason": null
},
"meta": {
"request_id": "req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f",
"platform": "spotify",
"source": "api",
"is_cached": false,
"credits_charged": 1,
"latency_ms": 966,
"fetched_at": "2026-10-25T12:00:00Z",
"input": null,
"credits_remaining": 9999,
"client_request_id": null,
"warnings": []
}
}import { SocialTap } from "socialtap";
const client = new SocialTap(); // apiKey defaults to process.env.SOCIALTAP_API_KEY
const res = await client.spotify.searchPlaylists.list({ query: "workout" });
console.log(res.data);
console.log(res.credits.charged, res.meta.request_id);import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws Exception {
String query = "query=" + encode("workout");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://socialtap.dev/v1/spotify/search_playlists?" + query))
.header("x-api-key", System.getenv("SOCIALTAP_API_KEY"))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// An error is {"error": {"code", "message", ...}}; success is {data, meta}, meta.credits_charged its price.
if (response.statusCode() >= 400) throw new RuntimeException(response.body());
System.out.println(response.body());
}
static String encode(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
}using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("SOCIALTAP_API_KEY"));
var query = string.Join("&",
"query=" + Uri.EscapeDataString("workout"));
var res = await http.GetAsync("https://socialtap.dev/v1/spotify/search_playlists?" + query);
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
var error = body.RootElement.GetProperty("error");
throw new Exception($"{error.GetProperty("code")}: {error.GetProperty("message")}");
}
Console.WriteLine(body.RootElement.GetProperty("data"));
Console.WriteLine(body.RootElement.GetProperty("meta").GetProperty("credits_charged"));// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }, serde_json = "1"
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let res = reqwest::blocking::Client::new()
.get("https://socialtap.dev/v1/spotify/search_playlists")
.query(&[("query", "workout")])
.header("x-api-key", env::var("SOCIALTAP_API_KEY")?)
.send()?;
let ok = res.status().is_success();
let body: serde_json::Value = res.json()?;
if !ok {
return Err(format!("{}: {}", body["error"]["code"], body["error"]["message"]).into());
}
println!("{} {}", body["data"], body["meta"]["credits_charged"]);
Ok(())
}200 OK · GET https://socialtap.dev/v1/spotify/search_playlistsLong arrays and strings are shortened here. Real responses are complete.Docs One schema on 6 platforms.
The response reference lists every field, its type and when it is null. See the full schema.
Null means unknown
A field Spotify doesn't show is null, never zero or an empty string, so a real zero always means zero.
What it accepts
What to search for, as in Spotify's search box: a playlist's title, a mood, a genre or an artist, in any language.
Write one parser
Spotify, TikTok, Instagram and 3 more platforms answer in these fields, so the code that reads one reads them all.
platformstring- Platform ID (tiktok, youtube, facebook, …).
idstring- Platform-native playlist ID (string): TikTok's playlist (mix) or collection ID, YouTube's list ID (PL…), Facebook's video list ID.
urlstring- Canonical playlist URL.
typestring- playlist: a list its owner curates and orders (TikTok playlists, YouTube playlists, Facebook playlists and series); collection: posts an account saved from any creator into a named public folder (TikTok collections). New values may be added; treat unknown values as playlist.
platform_typestring- The platform's own label, lowercase (mix, collection, playlist, series, podcast, …).
titlestring- Name its owner gave it.
descriptionstring- Description as plain text; null when it has none or the platform shows none (TikTok).
ownerobject- The account that curates it (for a collection, the account that saved the posts, not their authors). Where a platform's curators are not accounts (Apple Music), what it states of them: a name, perhaps a page and a picture, with id and username null, and every field null when it names no one.
Live data from 33 platforms.
What teams build with the Spotify Playlist Search API.
Find curators to pitch a song to
Find playlists for a mood or genre
Turn a playlist title into its ID
Good to know before you build.
- Results are playlists only: the tracks, artists, albums and podcasts a query matches aren't in the list.
- Search lists no item count, follower count, dates or collaborative flag, so metrics.post_count and those platform_specific.spotify fields are null: the Spotify Playlist API returns them.
- platform_type is playlist, or Spotify's own name for it when it has one, such as editorial, descripto, inspiredby-mix, topic-mix, artist-mix-reader and artistsets.
- title is null on a playlist whose title is empty or only spaces. It is still a real playlist, listed with its ID, link and owner.
- is_algorithmic is null where Spotify says neither, as on a listener's playlist.
- Results are in Spotify's order, which depends on the page size, so a smaller limit isn't always the top of a larger one. A walk ends with page.end_reason platform_limit where Spotify stops serving, however many it matched.
- A page can hold fewer playlists than its limit while next_cursor goes on: repeats from the page before are dropped, and so are playlists Spotify no longer serves or whose owner it no longer names.
- A page left with nothing is Spotify failing, not the end of the list: a first page then comes from the fallback below and a later one fails with a retryable error. The exception is a page that goes on with only a few such playlists dropped (limit=1 can hit one), which returns empty with a next_cursor.
- Spotify answers a weak query with its nearest guesses, so an empty page is rare and any result may be a loose match. Its music filters return an empty page here, since no playlist has them, and owner: and user: are read as words, so a search can't be narrowed to one owner.
- When Spotify's search doesn't answer, the first pages come from a web search engine's index, each playlist read from Spotify, with a partial_data warning, and owner.avatar_url is null. That list holds only what the index has, often a small part of Spotify's, in the engine's order. A next_cursor stays on the list it came from: if Spotify's search stops answering mid-walk, the next page fails with a retryable error instead of switching lists.
- An empty query, a Spotify URI or a link to a Spotify page returns invalid_input, naming the endpoint that takes it where there is one. For a link to Spotify's search page, send its words instead.
1 credit per page. Failed requests are free.
Buy credits once and spend them on any endpoint, on any platform.
| Source | Price per successful page | Per 1,000 pages |
|---|---|---|
| Any source | 1 credit | $0.75–$1.97 |
- Cache hits are free.
- Credits from packs and top-ups never expire.
- A monthly plan's credits are spent first, and what is left of them ends at each renewal.
- Prices exclude tax: sales tax is added at checkout where it applies.
- Outside the US, plans and packs are sold to businesses: checkout asks for your VAT, GST or other business tax ID.
Questions about the Spotify Playlist Search API.
More in the API reference.
How much does the Spotify Playlist Search API cost?
How do I get the next page?
Try it with your own search.
Type it in the playground, or copy the request into your code.
- No credit card
- Failed requests are free
- Works on all 218 endpoints