# Suno Scraper - Songs, Playlists, Creators & Lyrics (`abotapi/suno-music-scraper`) Actor

Collect public Suno music data: songs with lyrics, style tags, model version, play and like counts, audio, video and cover art links, plus playlist and creator records. Choose the curated Explore sections, specific playlists, or a creator's full public catalogue.

- **URL**: https://apify.com/abotapi/suno-music-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** AI, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.30 / 1,000 result records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Suno Scraper: Songs, Playlists, Creators and Lyrics

Pull public music data from Suno (suno.com) into a clean, flat dataset. Every song record carries the title, the full lyric sheet, the style prompt, the model version, play and like counts, the creator identity, and direct links to the audio, the video and the cover art. Point the actor at the curated discovery sections, at any public playlist, or at a creator handle, and it returns records up to the cap you set.

### Why this scraper

- **Four record kinds in one dataset**: songs, playlists, creator profiles and contests, told apart by a `recordType` column, so a single run can map a whole corner of the catalogue.
- **40+ fields per song**, including the complete lyric sheet, the style prompt, duration, model version, remix and stem availability, and the audio, video, cover and preview links.
- **Three ways in**: the curated discovery sections, a list of playlist links, or a list of creator handles. Creator mode can optionally follow every playlist linked on that profile.
- **Full depth, not just the first screen**: a large playlist or a prolific creator's catalogue comes back complete, up to your record cap, rather than stopping at the first batch.
- **One predictable cap**: `maxItems` is the only limit that stops a run, and it is shared fairly across the sources you asked for, so the last playlist in your list is never starved by the first.
- **Lyrics are optional**: turn them off for a dataset that is a fraction of the size when you only need titles, styles and counts.
- **Built for schedules**: turn on recurring updates and later runs return only what is NEW, UPDATED or REAPPEARED, each labelled in a changeType column, so a daily job does not re-deliver the same catalogue.
- **Interrupted runs can be continued**: paste the previous run id and the records it already returned are skipped.
- **Pay per result**: you are billed for the records you receive, not for time spent.

### Data you get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
|---|---|
| `recordType` | `song` |
| `id` | `00000000-0000-0000-0000-000000000001` |
| `url` | `suno.com/song/00000000-0000-0000-0000-000000000001` |
| `title` | `Sample Song Title` |
| `createdAt` | `2026-01-01T00:00:00.000Z` |
| `creatorHandle` | `samplecreator` |
| `creatorDisplayName` | `Sample Creator` |
| `creatorProfileUrl` | `suno.com/@samplecreator` |
| `creatorIsVerified` | `false` |
| `playCount` | `0` |
| `upvoteCount` | `0` |
| `commentCount` | `0` |
| `durationSeconds` | `180.0` |
| `styleTags` | `synth pop, driving bass, 120 BPM, male vocals` |
| `displayTags` | `["synth pop", "dance-pop"]` |
| `lyrics` | `[Verse 1]\nSample lyric line here\n\n[Chorus]\n...` |
| `modelName` | `sample-model` |
| `modelVersion` | `v6` |
| `isInstrumental` | `false` |
| `isRemix` | `false` |
| `canRemix` | `true` |
| `hasStems` | `false` |
| `isExplicit` | `false` |
| `imageUrl` | `https://cdn0.example.com/image_00000000.jpeg` |
| `audioUrl` | `https://cdn0.example.com/clip/00000000.m4a` |
| `videoUrl` | `https://cdn0.example.com/00000000.mp4` |
| `playlistPosition` | `0` |
| `sourceType` | `playlist` |
| `sourceTitle` | `Sample Playlist` |
| `scrapedAt` | `2026-01-01T00:00:00Z` |

Playlist records add `description`, `songCount`, `totalDurationSeconds`, `upvoteCount`, `playCount` and the owning creator. Creator records add `followersCount`, `followingCount`, `totalPlays`, `totalUpvotes`, `profileDescription` and a `playlists` array you can feed straight back into playlists mode. Contest records add `startTime`, `endTime`, `judgingStart`, `judgingEnd`, `winnersAnnouncedDate` and the submissions playlist link.

### How to use

Collect from the curated discovery sections (the default):

```json
{
  "mode": "explore",
  "maxItems": 50
}
```

Pin one named section instead of all of them:

```json
{
  "mode": "explore",
  "exploreFeeds": ["trending"],
  "maxItems": 100
}
```

Collect from two playlists, sharing the cap between them:

```json
{
  "mode": "playlists",
  "playlistUrls": [
    "0d597d0c-cdb2-4f9c-b4da-57931929f0d0",
    "990fd5fe-70d2-449b-8a4d-3cb0a7d3e805"
  ],
  "maxItems": 500
}
```

Pull a creator's whole public catalogue plus every playlist on their profile, without lyrics:

```json
{
  "mode": "profiles",
  "profileUrls": ["@samplecreator"],
  "profileSortBy": "play_count",
  "followProfilePlaylists": true,
  "includeLyrics": false,
  "maxItems": 0
}
```

Playlist links and bare ids are both accepted, and so are `@handle`, a bare handle and a full creator page link.

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `explore` | `explore`, `playlists` or `profiles`. Each mode reads only its own fields. |
| `exploreFeeds` | array | empty | Explore mode only. Section ids to read. Empty means every section the discovery page currently offers. |
| `playlistUrls` | array | empty | Playlists mode only. Playlist page links or bare playlist ids. |
| `profileUrls` | array | empty | Profiles mode only. Creator page links, `@handle` or a bare handle. |
| `profileSortBy` | string | `upvote_count` | Order of a creator's songs and playlists: `upvote_count`, `created_at` or `play_count`. |
| `followProfilePlaylists` | boolean | `false` | Profiles mode only. After the creator's own songs, also include every public playlist listed on that profile. |
| `includeLyrics` | boolean | `true` | Include the full lyric sheet on every song record. |
| `maxItems` | integer | `50` | The single record cap for the whole run. `0` means no limit. |
| `maxPages` | integer | `200` | Runaway guard: an upper bound on how much is read for any one playlist, profile or section. |
| `resumeFromRunId` | string | empty | Run id (or dataset id) of an earlier run of this Actor to continue. Records that run returned are skipped. |
| `incrementalMode` | boolean | `false` | Recurring monitoring. Later runs of the same setup return only what changed, labelled in `changeType`. |
| `stateKey` | string | empty | Optional name for a monitoring campaign, so its memory stays stable or is deliberately shared. |
| `emitUnchanged` | boolean | `false` | Also return records that did not change, labelled `UNCHANGED`. Returns and bills extra rows. |
| `emitExpired` | boolean | `false` | Also return records that are no longer found, labelled `EXPIRED`. Only produced by a complete run. |
| `mcpConnectors` | array | empty | Optional. Send results into your apps after the scrape. |
| `notionParentPageUrl` | string | empty | Optional. Notion page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Optional. Cap on items written to each connector. |
| `proxyConfiguration` | object | Apify Proxy | Connection used for every request. |

### Output example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "recordType": "song",
  "id": "00000000-0000-0000-0000-000000000001",
  "url": "suno.com/song/00000000-0000-0000-0000-000000000001",
  "title": "Sample Song Title",
  "createdAt": "2026-01-01T00:00:00.000Z",
  "status": "complete",
  "entityType": "song_schema",
  "creatorHandle": "samplecreator",
  "creatorDisplayName": "Sample Creator",
  "creatorUserId": "00000000-0000-0000-0000-000000000002",
  "creatorProfileUrl": "suno.com/@samplecreator",
  "creatorAvatarImageUrl": "https://cdn0.example.com/00000000.jpeg",
  "creatorIsVerified": false,
  "playCount": 0,
  "upvoteCount": 0,
  "dislikeCount": null,
  "commentCount": 0,
  "flagCount": 0,
  "durationSeconds": 180.0,
  "styleTags": "synth pop, driving bass, 120 BPM, male vocals",
  "displayTags": ["synth pop", "dance-pop"],
  "lyrics": "[Verse 1]\nSample lyric line here\n\n[Chorus]\nSample chorus line here",
  "modelName": "sample-model",
  "modelVersion": "v6",
  "isInstrumental": false,
  "isRemix": false,
  "canRemix": true,
  "hasStems": false,
  "generationType": "gen",
  "isPublic": true,
  "isExplicit": false,
  "allowComments": true,
  "hasHook": false,
  "isContestEntry": false,
  "imageUrl": "https://cdn0.example.com/image_00000000.jpeg",
  "imageLargeUrl": "https://cdn0.example.com/image_large_00000000.jpeg",
  "videoUrl": "https://cdn0.example.com/00000000.mp4",
  "audioUrl": "https://cdn0.example.com/clip/00000000.m4a",
  "mediaUrls": [
    { "url": "https://cdn0.example.com/clip/00000000.m4a", "content_type": "m4a-opus" }
  ],
  "albums": [],
  "playlistCaption": null,
  "playlistAddedAt": "2026-01-01T00:00:00.000Z",
  "playlistPosition": 0,
  "sourceType": "playlist",
  "sourceId": "00000000-0000-0000-0000-000000000003",
  "sourceTitle": "Sample Playlist",
  "sourceUrl": "suno.com/playlist/00000000-0000-0000-0000-000000000003",
  "metadataRaw": { "duration": 180.0, "tags": "synth pop, driving bass" },
  "scrapedAt": "2026-01-01T00:00:00Z"
}
```

### Recurring updates and continuing a run

Two separate features, and they are not interchangeable.

**Continue a previous run.** Paste an earlier run id (or dataset id) into `resumeFromRunId`. Every record that run already returned is skipped, so an interrupted large collection can finish without paying for the same rows twice.

**Recurring updates.** Turn on `incrementalMode` for a scheduled job. The first run returns everything as `NEW`. Every later run of the same setup compares against what it saw last time and labels each record:

| `changeType` | Meaning |
|---|---|
| `NEW` | First time this record has been seen for this setup. |
| `UPDATED` | Seen before, and something on it changed. `changedFields` names what. |
| `UNCHANGED` | Seen before and identical. Withheld by default; set `emitUnchanged` to receive it. |
| `REAPPEARED` | Was reported gone, and is back. |
| `EXPIRED` | Was there before and is no longer found. Set `emitExpired` to receive it. |

Two things worth knowing before you schedule it:

- Play, like and comment counts move constantly on an active catalogue, so a record whose only difference is a counter still counts as `UPDATED`. Records are compared on their content; the collection time and which section they were reached through are ignored.
- Records are only ever reported as `EXPIRED` by a run that covered its whole setup. A run stopped by the record cap, a run that continued a previous one, a run where a source did not finish, and a run that collected nothing all skip gone-record detection entirely, and say so in the log. That is deliberate: absence proves nothing when the run was cut short.

Set `stateKey` to name a campaign. Leave it empty and the memory is keyed on the run setup automatically, so two differently configured schedules never overwrite each other.

### Send results into your apps (MCP connectors)

Results can optionally be piped into apps you already use, through Model Context Protocol connectors, as a delivery step after the scrape. Authorize a connector under Apify, Settings, API and Integrations, then select it in the `mcpConnectors` field of the input. For Notion, also set `notionParentPageUrl` to the page under which item pages should be created; `maxNotifyListings` caps how many items are written to each connector.

What arrives in the connector is a condensed, human readable summary per item: a title plus the key fields flattened to plain text. Nested objects collapse to their main value and arrays are trimmed to a few entries. The complete record always stays in the Apify dataset, which is never changed by this step. Leaving `mcpConnectors` empty skips the export entirely.

### Plan requirement

The actor runs on any Apify account. It uses the standard Apify Proxy setting shipped in the input, which is available on every plan; you can change it if you have a reason to.

# Actor input Schema

## `mode` (type: `string`):

Where to collect from: the curated Explore sections, specific playlists, or creator profiles.

## `exploreFeeds` (type: `array`):

Only read when mode = explore. Leave empty to cover every curated section the discovery page currently offers. Fill it to pin specific sections, either by their section id or by a playlist id.

## `playlistUrls` (type: `array`):

Only read when mode = playlists. Paste playlist page links or bare playlist ids. Each playlist is read in full, up to the record cap.

## `profileUrls` (type: `array`):

Only read when mode = profiles. Paste creator profile links or bare handles such as @suno. Each creator's public catalogue is read in full, up to the record cap.

## `profileSortBy` (type: `string`):

Order in which a creator's songs and playlists are returned.

## `followProfilePlaylists` (type: `boolean`):

After a creator's own songs, also include every public playlist listed on that creator's profile.

## `includeLyrics` (type: `boolean`):

Include the full lyric sheet on every song record. Turn off for a much smaller dataset when you only need titles, styles and counts.

## `maxItems` (type: `integer`):

Stop after this many records across the whole run (0 = no limit). Songs, playlist headers, creator profiles and contests all count as records.

## `maxPages` (type: `integer`):

Runaway guard: an upper bound on how much is read for any single playlist, profile or section. Leave at the maximum so the record cap above is the only real limit.

## `resumeFromRunId` (type: `string`):

Paste the run id (or dataset id) of an earlier run of this Actor to continue it. Records that run already returned are skipped, so an interrupted large collection can finish without paying for the same rows twice. Leave empty for a normal run.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or weekly monitoring. The first run returns everything as NEW. Later runs return only NEW, UPDATED and REAPPEARED records, each labelled in a changeType column. Note that play, like and comment counts move constantly on an active catalogue, so a record whose only difference is a counter still counts as UPDATED. Leave off for a normal one-off run.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its memory stable, or to deliberately share one memory across differently configured runs. Leave empty and the memory is keyed on the run setup automatically.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, labelled UNCHANGED. This returns, and bills, rows you already have.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present before and are no longer found, labelled EXPIRED. Only produced when a run covered its whole setup, so a capped or continued run never reports records as gone. This returns, and bills, extra rows.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output.

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## `proxyConfiguration` (type: `object`):

Connection used for every request. The default works on every plan.

## Actor input object example

```json
{
  "mode": "explore",
  "exploreFeeds": [
    "trending"
  ],
  "playlistUrls": [
    "https://suno.com/playlist/0d597d0c-cdb2-4f9c-b4da-57931929f0d0"
  ],
  "profileUrls": [
    "https://suno.com/@suno"
  ],
  "profileSortBy": "upvote_count",
  "followProfilePlaylists": false,
  "includeLyrics": true,
  "maxItems": 20,
  "maxPages": 200,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "explore",
    "exploreFeeds": [
        "trending"
    ],
    "playlistUrls": [
        "https://suno.com/playlist/0d597d0c-cdb2-4f9c-b4da-57931929f0d0"
    ],
    "profileUrls": [
        "https://suno.com/@suno"
    ],
    "profileSortBy": "upvote_count",
    "followProfilePlaylists": false,
    "includeLyrics": true,
    "maxItems": 20,
    "maxPages": 200,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "maxNotifyListings": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/suno-music-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "explore",
    "exploreFeeds": ["trending"],
    "playlistUrls": ["https://suno.com/playlist/0d597d0c-cdb2-4f9c-b4da-57931929f0d0"],
    "profileUrls": ["https://suno.com/@suno"],
    "profileSortBy": "upvote_count",
    "followProfilePlaylists": False,
    "includeLyrics": True,
    "maxItems": 20,
    "maxPages": 200,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "maxNotifyListings": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/suno-music-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "explore",
  "exploreFeeds": [
    "trending"
  ],
  "playlistUrls": [
    "https://suno.com/playlist/0d597d0c-cdb2-4f9c-b4da-57931929f0d0"
  ],
  "profileUrls": [
    "https://suno.com/@suno"
  ],
  "profileSortBy": "upvote_count",
  "followProfilePlaylists": false,
  "includeLyrics": true,
  "maxItems": 20,
  "maxPages": 200,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/suno-music-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/suno-music-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3RWjLzhWks8fVwT3a/builds/UAvWTg62UUbNWDLMu/openapi.json
