# Udio Scraper - Prompts, Lyrics, Tags, Audio (`abotapi/udio-com-scraper`) Actor

Collect public Udio tracks with the complete generation prompt, full lyrics, style tags, like and play counts, duration, artwork and a direct audio link. Search the catalogue by keyword and ordering, read a list of track addresses, or pull a public playlist in its original order.

- **URL**: https://apify.com/abotapi/udio-com-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.00 / 1,000 track results

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

## Udio Scraper

Collect public tracks from Udio together with the text that made them: the
complete generation prompt, the full lyrics, and the style tags. Those three
fields are kept exactly as the creator wrote them, because they are usually the
whole point of collecting Udio tracks and they are the first thing a lossy
export drops.

Every row also carries the artwork, a direct audio link, like and play counts,
duration, timestamps and the creator handle.

### What you can collect

| Mode | What it does | What you supply |
|---|---|---|
| Search the catalogue | Finds public tracks by keyword, in the order you choose | A keyword (optional) and an ordering |
| Specific tracks | Reads a list of tracks you already know | Track page addresses, or bare identifiers |
| Playlist | Reads every track in one or more public playlists, in playlist order | Playlist page addresses, or bare identifiers |

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `mode` | select | `search` | `search`, `songIds` or `playlist` |
| `searchTerm` | text | empty | Matched against titles, prompts and tags. Empty browses everything |
| `sortBy` | select | `newest` | `newest`, `mostLiked` or `mostPlayed` |
| `trackIds` | list | empty | One track per line, address or identifier |
| `playlistIds` | list | empty | One playlist per line, address or identifier |
| `maxItems` | number | `20` | Stop after this many tracks. `0` collects everything available |
| `resumeFromRunId` | text | empty | Continue one interrupted run without returning what it already saved |
| `incrementalMode` | checkbox | off | Return only what changed since the last run of the same selection |
| `emitUnchanged` | checkbox | off | Also return unchanged tracks, marked `UNCHANGED` (billed) |
| `emitExpired` | checkbox | off | Also return tracks that are gone, marked `EXPIRED` (billed) |
| `stateKey` | text | empty | Name a monitoring campaign, or share one across runs |
| `mcpConnectors` | list | empty | Optional MCP connectors to also send the results to. Empty skips the export |
| `notionParentPageUrl` | text | empty | Notion connector only: the page under which track pages are created |
| `maxNotifyListings` | number | `50` | How many tracks each connector receives. Does not affect the dataset |
| `proxyConfiguration` | proxy | Apify Proxy | The default works on every plan |

Both list fields accept either shape, so you can paste addresses straight from
your browser or feed in bare identifiers from an earlier run.

#### Resume and recurring updates

These are two different things.

**Resume** finishes ONE interrupted collection. Paste that run's ID (or its
dataset ID) into `resumeFromRunId` and the tracks it already saved are skipped.

**Incremental mode** is for running the same selection again and again, daily
say, to watch a playlist gain and lose tracks. The actor remembers the previous
run of that selection and, from the second run on, returns only what changed.
Each row then carries `changeType`, `changedFields`, `firstSeenAt` and
`lastSeenAt`. Memory is kept per selection (mode, keyword, ordering and lookup
list); set `stateKey` to name a campaign or to deliberately share one.

`EXPIRED` rows are only produced when a run scanned the whole selection. A run
stopped by `maxItems`, a keyword search that hit the 200-track ceiling, a
collection that could not be read, and a resumed run all skip that step and say
so in the log: a partial scan must never report a live track as gone.

### Output

| Field | Description |
|---|---|
| `trackId` | Stable identifier for the track |
| `url` | Track page address |
| `title` | Track title |
| `artist` | Creator handle shown on the track |
| `artistImageUrl` | Creator avatar image |
| `creatorId` | Stable identifier for the creator |
| `prompt` | The full generation prompt, kept verbatim |
| `lyrics` | The complete lyrics, kept verbatim, with section markers intact |
| `tags` | Style tags attached to the track |
| `userTags` | Tags added by the creator |
| `description` | Free text description |
| `attribution` | Attribution text, when the creator set one |
| `likeCount` | Number of likes |
| `playCount` | Number of plays |
| `durationSeconds` | Length in seconds |
| `createdAt` | When the track was generated |
| `publishedAt` | When the track was made public |
| `audioUrl` | Direct link to the audio file |
| `videoUrl` | Direct link to the video, when one exists |
| `artworkUrl` | Cover artwork image |
| `styleId` | Identifier of the style used |
| `styleSourceType` | Where the style came from |
| `styleSourceTrackId` | Track the style was taken from, when applicable |
| `parentTrackId` | Track this one extends or remixes, when applicable |
| `generationId` | Identifier of the generation batch |
| `generationStatus` | Status reported for the generation |
| `isFinished` | Whether generation completed |
| `isPublishable` | Whether the track may be shared |
| `collectionId` | Playlist identifier, in playlist mode |
| `collectionName` | Playlist name, in playlist mode |
| `collectionUrl` | Playlist page address, in playlist mode |
| `positionInCollection` | Place in the playlist, in playlist mode |
| `sourceMode` | Which mode produced the row |
| `scrapedAt` | When the row was collected |
| `raw` | Every field exactly as the site supplied it, so nothing is lost |
| `changeType` | Incremental mode only: `NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED` |
| `changedFields` | Incremental mode only: which columns moved since the last run |
| `firstSeenAt` | Incremental mode only: when this track was first collected |
| `lastSeenAt` | Incremental mode only: when it was last collected |

#### Sample output

```
{
  "trackId": "00000000-0000-4000-8000-000000000000",
  "url": "https://www.udio.com/songs/00000000-0000-4000-8000-000000000000",
  "title": "Midnight Coffee Loop",
  "artist": "Jane Doe",
  "creatorId": "00000000-0000-4000-8000-000000000001",
  "prompt": "lofi, jazzy chill cafe vibes, warm tape saturation",
  "lyrics": "[Verse 1]\nRain on the window, cups going cold\n\n[Chorus]\nStay for one more song",
  "tags": ["chill", "jazz", "downtempo", "instrumental"],
  "userTags": ["study"],
  "description": "",
  "likeCount": 128,
  "playCount": 4210,
  "durationSeconds": 131.2,
  "createdAt": "2026-01-05T09:12:44+00:00",
  "publishedAt": "2026-01-05T09:15:02+00:00",
  "audioUrl": "https://example.com/audio/sample.mp3",
  "artworkUrl": "https://example.com/art/sample.png",
  "isFinished": true,
  "isPublishable": true,
  "sourceMode": "search",
  "scrapedAt": "2026-01-06T11:00:00+00:00"
}
```

### 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 collection. 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 track pages should be created; `maxNotifyListings` caps
how many tracks are written to each connector.

What arrives in the connector is a condensed, human readable summary per track:
a title plus the key fields flattened to plain text. Nested objects collapse to
their main value and long lists are trimmed to a few entries, so the prompt and
lyrics may be shortened there. The complete record always stays in the Apify
dataset, which this step never changes. Leaving `mcpConnectors` empty skips the
export entirely.

### Good to know

- A single catalogue search reaches at most 200 tracks, because that is as far
  as the site's own search view goes. To collect more, run several searches
  with narrower keywords instead of raising `maxItems`.
- Playlist mode uses the playlist's own track list, so it returns every member
  of the playlist rather than the shorter preview the site shows elsewhere.
- Tracks that have been removed or made private are reported in the run log and
  simply skipped, so a short result is never silent.
- A very rare or very long keyword can make the site's own search time out
  rather than answer. The run stops with a clear message when that happens, so
  it is never reported as a successful empty result. Try a broader keyword.
- `maxItems` defaults to `20` to keep a first run quick and cheap. Set it to
  `0` when you want everything.

### Support

Found a track that does not come back correctly, or a field you need that is
missing? Open an issue from the Actor page and include the input you used.

# Actor input Schema

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

Choose how tracks are found. 'Search the catalogue' browses public tracks by keyword and ordering. 'Specific tracks' reads a list of track addresses you already have. 'Playlist' reads every track in one or more public playlists.

## `searchTerm` (type: `string`):

Words to match against track titles, prompts and tags. Leave empty to browse the whole public catalogue in the chosen order. Used in 'Search the catalogue' mode.

## `sortBy` (type: `string`):

The order in which matching tracks are returned.

## `trackIds` (type: `array`):

One track per line. Paste the full address of a track page, or just its identifier. Used in 'Specific tracks' mode.

## `playlistIds` (type: `array`):

One playlist per line. Paste the full address of a public playlist page, or just its identifier. Every track the playlist contains is collected, in the playlist's own order. Used in 'Playlist' mode.

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

Stop after this many tracks. Set to 0 to collect everything available for your selection. A single catalogue search can reach at most 200 tracks; use a narrower keyword to reach different ones.

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

Paste a previous run ID or dataset ID to continue a collection that was interrupted, without returning the tracks it already saved. Use this for ONE unfinished run; for recurring updates use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring, for example to watch a playlist gain and lose tracks or a track's like count move. The first run returns every track as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED tracks. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). The memory is kept separately for each mode, keyword, ordering and lookup list; use State key to name or deliberately share a monitoring campaign.

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

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

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

Off by default. Turn on to also return tracks that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has scanned the whole selection: not when Maximum tracks capped it, not when a keyword search hit the 200-track ceiling, not when part of the selection could not be read, and not when Resume was used. This returns, and bills, extra rows.

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

Optional. Name this monitoring campaign to keep its memory stable, or deliberately share one memory across differently configured runs. Leave empty to let the actor derive a key automatically from the mode, keyword, ordering and lookup list.

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

Optionally send collected tracks 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-track 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 track pages are created. Required to enable the Notion export; ignored by other connectors.

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

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

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

Connection settings for the run. The default works on every plan and is recommended.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerm": "lofi",
  "sortBy": "newest",
  "trackIds": [
    "https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7"
  ],
  "playlistIds": [
    "https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"
  ],
  "maxItems": 20,
  "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 = {
    "trackIds": [
        "https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7"
    ],
    "playlistIds": [
        "https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"
    ],
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "maxNotifyListings": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/udio-com-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 = {
    "trackIds": ["https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7"],
    "playlistIds": ["https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"],
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "maxNotifyListings": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/udio-com-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 '{
  "trackIds": [
    "https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7"
  ],
  "playlistIds": [
    "https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"
  ],
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}' |
apify call abotapi/udio-com-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/udio-com-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/4DvdpEh6Mbmb165Vs/builds/N2ye3WA88yR32RgD6/openapi.json
