Suno Scraper - Songs, Playlists, Creators & Lyrics
Pricing
from $1.30 / 1,000 result records
Suno Scraper - Songs, Playlists, Creators & Lyrics
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.
Pricing
from $1.30 / 1,000 result records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
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
recordTypecolumn, 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:
maxItemsis 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):
{"mode": "explore","maxItems": 50}
Pin one named section instead of all of them:
{"mode": "explore","exploreFeeds": ["trending"],"maxItems": 100}
Collect from two playlists, sharing the cap between them:
{"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:
{"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.
{"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
EXPIREDby 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.