# 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
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Udio Scraper: Prompts, Lyrics, Tags & Audio

Udio Scraper turns Udio into a clean AI music data feed. Collect the exact generation prompt behind each public track, the complete lyrics with section markers intact, and the style tags the creator chose, together with the artwork, a direct audio link, like and play counts, duration, timestamps and the creator handle. Search the public catalogue by keyword and ordering, look up specific tracks by address, or pull every track in one or more public playlists in their own order, then export to JSON, CSV or Excel, or read the results straight through the API.

### Why This Scraper?

- **Three ways to find tracks.** Search the catalogue by keyword and ordering, paste track addresses you already have, or read every track in one or more public playlists.
- **Creative text kept verbatim.** The full prompt, complete lyrics and style tags are carried through exactly as the creator wrote them, never trimmed or summarised.
- **Nothing hand-picked is lost.** Every field the platform sends comes through in `raw` too, so a new upstream field reaches your dataset the day it appears.
- **True playlist order.** Playlist mode reads the collection's own member list rather than the shorter preview the catalogue view shows, and restores the playlist's own order on every row.
- **Built for schedules.** Incremental mode returns only new, updated, reappeared and gone tracks on recurring runs, and unchanged tracks are not billed.
- **Resume without paying twice.** Finish one interrupted run by pointing at its run or dataset ID; tracks it already saved are skipped.
- **A refused run fails loudly.** If the site cannot be read, the run stops with a clear message instead of quietly returning an empty dataset.

### Use Cases

- **AI music researchers and dataset builders:** collect prompt-to-track pairs at scale to study how wording shapes generated music.
- **Playlist curators and fans:** track a playlist's full membership, order and engagement over time.
- **Music blogs and content teams:** pull lyrics, tags and artwork to write about trending AI-generated tracks.
- **Trend watchers:** schedule recurring runs to see which tracks are gaining likes and plays.
- **Archivists:** keep a durable copy of a track's prompt and lyrics before it is edited or removed by its creator.

### Data You Get

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

| Field | Example |
| --- | --- |
| `trackId` | `"00000000-0000-4000-8000-000000000000"` |
| `url` | `"https://www.udio.com/songs/00000000-0000-4000-8000-000000000000"` |
| `title` | `"Midnight Coffee Loop"` |
| `artist` / `creatorId` | `"Jane Doe"` / `"00000000-0000-4000-8000-000000000001"` |
| `artistImageUrl` | creator avatar image |
| `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` / `userTags` | `["chill", "jazz", "downtempo", "instrumental"]` / `["study"]` |
| `description` / `attribution` | free text, when the creator wrote one |
| `likeCount` / `playCount` | `128` / `4210` |
| `durationSeconds` | `131.2` |
| `createdAt` / `publishedAt` | when the track was generated / made public |
| `audioUrl` | direct link to the audio file |
| `videoUrl` | direct link to the video, when one exists |
| `artworkUrl` | cover artwork image |
| `styleId` / `styleSourceType` / `styleSourceTrackId` | which style produced the track, when applicable |
| `parentTrackId` | the track this one extends or remixes, when applicable |
| `generationId` / `generationStatus` | the generation batch and its reported status |
| `isFinished` / `isPublishable` | whether generation completed / may be shared |
| `collectionId` / `collectionName` / `collectionUrl` / `positionInCollection` | playlist context; filled only in playlist mode (`null` in the other modes, where `collectionUrl` is left out) |
| `sourceMode` | which mode produced the row |
| `scrapedAt` | when the row was collected |
| `raw` | every field exactly as the platform sent it |

### How to Use

1. Pick a **mode**: Search the catalogue (by keyword and ordering), Specific tracks (paste addresses or bare identifiers) or Playlist (paste playlist addresses or identifiers).
2. Fill in the fields for that mode.
3. Set **Maximum tracks** to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API. The dataset offers an overview table and, for Incremental mode runs, a **Changes** view showing `changeType` and `changedFields` for each track.

#### Supported address shapes

| Shape | Example | Field |
| --- | --- | --- |
| Track page | `https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7` | `trackIds` |
| Track page without www (redirects to www) | `https://udio.com/songs/4b109fab-fd20-46e3-8b2e-49a3a3f57786` | `trackIds` |
| Playlist page | `https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627` | `playlistIds` |
| Bare identifier | `9765efef-43b4-4c0e-a3a0-ce288c99ccc7` | either |

Creator, search and profile pages are not supported, and a track address placed in the playlist field (or the reverse) is skipped. Each such entry is reported with a warning in the run log instead of failing silently.

**Behaviour change:** if you choose Specific tracks or Playlist mode and leave the address field untouched, the run now reads one example per shape (two tracks, or the example playlist) instead of stopping with an error. Provide your own addresses to read something else.

**Search a keyword, newest first:**

```json
{
  "mode": "search",
  "searchTerm": "lofi",
  "sortBy": "newest",
  "maxItems": 20
}
```

**Look up specific tracks:**

```json
{
  "mode": "songIds",
  "trackIds": [
    "https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7",
    "baf84bf6-ff44-474b-aa4a-1e779776770d"
  ]
}
```

**Read a whole public playlist, in its own order:**

```json
{
  "mode": "playlist",
  "playlistIds": ["https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"],
  "maxItems": 0
}
```

**Browse the whole catalogue, most liked first:**

```json
{
  "mode": "search",
  "sortBy": "mostLiked",
  "maxItems": 50
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/udio-com-scraper").call(run_input={"mode": "search", "searchTerm": "lofi", "maxItems": 20})
for track in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(track["title"], track["prompt"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/udio-com-scraper').call({ mode: 'search', searchTerm: 'lofi', maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### The 200-track catalogue ceiling

A single catalogue query (one keyword and ordering, or the whole catalogue with no keyword) can reach at most 200 tracks, because that is as far as the site's own search view goes. Raising **Maximum tracks** past 200 does not reach more of that same query; run several searches with narrower keywords instead. Specific tracks and Playlist mode are not affected: they read an explicit, bounded set of identifiers rather than walking this ceiling.

#### Resume and recurring updates

These are two different things.

**Resume** finishes ONE interrupted run. Paste that run's ID (or its dataset ID) into `resumeFromRunId`; the tracks it already saved are skipped, so you don't pay twice. The new run still reads from the start of your selection, it simply does not re-publish what it recognises.

**Incremental mode** is for running the same selection again and again, daily say, to watch a playlist gain and lose tracks or a track's play count move. The actor remembers the previous run of that selection and, from the second run on, returns only what changed. Each row then carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields`, `firstSeenAt` and `lastSeenAt`. Memory is kept per selection (mode, keyword, ordering and lookup list); memory saved by an earlier version of the actor for the same selection is picked up automatically; set `stateKey` to name a campaign or to deliberately share one. Use `ignoreFieldsForChanges` to stop a noisy field (an output column such as `playCount`, which also covers its copy inside `raw`, or a nested key with a dotted name) from counting as a change; the scrape time is always ignored. Resume and an existing Incremental mode memory cannot be combined in the same run; the run refuses with a clear message and asks you to run them separately.

`EXPIRED` rows are only produced when a run scanned the whole selection: not when **Maximum tracks** was reached and stopped it early, not when a catalogue search reached the 200-track ceiling, not when part of the selection (a private track, or a collection that is not public) could not be read, and not on a resumed run. When any of these applies, the run marks nothing as gone at all, and its log says which reason applied. Because of the ceiling, a catalogue search can only mark tracks as gone when it has fewer than 200 matches; to watch a larger set for removals, use Specific tracks or Playlist mode.

If a track appears in two of the playlists you list, it is published once, attributed to (and positioned within) the first playlist in your list that contains it; it is not silently dropped, just not duplicated.

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

Optionally pipe the collected tracks into apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the collection: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each track, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text; nested objects collapse to their main value and long lists are trimmed, so the prompt and lyrics may be shortened there. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify > Settings > Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where track pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search`, `songIds`, or `playlist`. |
| `searchTerm` | string | (none) | Keyword matched against titles, prompts and tags (search mode). Empty browses the whole catalogue. |
| `sortBy` | string | `newest` | `newest`, `mostLiked`, or `mostPlayed` (search mode). |
| `trackIds` | array | sample | One track per line: a full track address or a bare identifier (specific tracks mode). |
| `playlistIds` | array | sample | One playlist per line: a full playlist address or a bare identifier (playlist mode). |
| `maxItems` | integer | `20` | Stop after this many tracks. `0` collects everything available for the selection. |
| `resumeFromRunId` | string | (none) | Continue one interrupted run without returning tracks it already saved. |
| `incrementalMode` | boolean | `false` | Return only new, updated, reappeared and gone tracks on recurring runs of the same selection. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) tracks unchanged since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Also return (and bill) tracks gone since the last run, marked `EXPIRED`. Only when the whole selection was scanned. |
| `stateKey` | string | (none) | Name or share an incremental-mode memory across runs. |
| `ignoreFieldsForChanges` | array | `[]` | Field names that never make a track `UPDATED` or appear in `changedFields` (incremental mode only). The fields stay in the output. An output column such as `playCount` also covers its copy inside `raw`. Use a dotted name such as `raw.someKey` for a key inside a nested object or list. Changing this list does not mark tracks as `UPDATED` by itself. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each track to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which track pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on tracks written to each connector per run. Does not affect the dataset. |
| `proxyConfiguration` | object | Apify Proxy | Connection settings for the run. |

### Output Example

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

```json
{
  "trackId": "00000000-0000-4000-8000-000000000000",
  "url": "https://www.udio.com/songs/00000000-0000-4000-8000-000000000000",
  "title": "Midnight Coffee Loop",
  "artist": "Jane Doe",
  "artistImageUrl": "https://example.com/avatar/sample.png",
  "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": "",
  "attribution": null,
  "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",
  "videoUrl": null,
  "artworkUrl": "https://example.com/art/sample.png",
  "styleId": null,
  "styleSourceType": "style",
  "styleSourceTrackId": null,
  "parentTrackId": null,
  "generationId": "00000000-0000-4000-8000-000000000002",
  "generationStatus": "succeeded",
  "isFinished": true,
  "isPublishable": true,
  "sourceMode": "search",
  "collectionId": null,
  "collectionName": null,
  "positionInCollection": null,
  "scrapedAt": "2026-01-06T11:00:00+00:00",
  "raw": {}
}
```

### Plan Requirement

The default Apify Proxy connection works out of the box on any plan that includes Apify Proxy access; no residential proxy is needed. The site occasionally refuses waves of default connections; when every attempt on the default pool is refused, the actor automatically retries once on a premium residential pool, so you do not need to change anything. If the connection itself cannot be set up or is refused for your account (for example a plan without proxy access, or invalid credentials in your own proxy URLs), the run stops with a message saying so; it never silently continues on a direct connection and never falls back to one. If you bring your own proxy, that connection is used exactly as configured with no automatic fallback; the actor only runs on a direct connection when you turn the proxy off yourself.

### FAQ

#### How much does it cost?

You pay per track returned; unchanged tracks in Incremental mode are not billed unless you turn that on. The **Pricing** tab shows the current rates. Use **Maximum tracks** to cap the cost of any run.

#### Is it legal to scrape Udio?

This actor collects only publicly available Udio data. You are responsible for how you use it: follow Udio's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Generated audio, lyrics and artwork may carry rights distinct from the plain metadata, so treat creative fields with the same care you would any third-party creative work.

#### Can I get only new or changed tracks on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. From the second run on, each run returns only new, updated, reappeared and gone tracks for that selection, and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### What is the difference between Resume and Incremental mode?

Resume finishes ONE interrupted run: paste its run or dataset ID into `resumeFromRunId` and already-saved tracks are skipped. Incremental mode compares each new run of the same selection against the previous one, on a schedule. They solve different problems and cannot be combined while an Incremental mode memory already exists for that selection; a run that tries fails with a message asking you to run them separately.

#### Why didn't a capped run mark any tracks as gone?

`EXPIRED` needs a complete look at the selection. A run stopped by **Maximum tracks**, a catalogue search that hit the 200-track ceiling, a private track or an unreadable playlist, and a resumed run all leave part of the selection unchecked, so none of them may declare anything gone that run; the log states which of these applied. For example, if 60 tracks in a 120-track playlist changed since the last run and **Maximum tracks** is 50, the run stops after returning 50 and emits no `EXPIRED` rows, even with **Emit expired** on. (In Incremental mode the cap counts only tracks actually returned, so a quiet day with fewer changes than the cap still counts as a full scan.) To get a real expiry sweep, periodically run the same selection with **Maximum tracks** set to `0` (or above the selection's size), alongside your smaller day-to-day capped runs. This works for Specific tracks, Playlist mode, and catalogue searches with fewer than 200 matches. A catalogue search with 200 or more matches always stops at the ceiling, so no setting of **Maximum tracks** lets it mark tracks as gone.

#### Why did my run fail instead of returning an empty dataset?

If the site refuses every request, or returns tracks this build cannot recognise at all, the run stops with a clear message so "nothing matched" is never confused with "nothing could be read". A keyword that genuinely matches nothing, or an incremental run where nothing changed, still succeeds with zero published rows. Before giving up, the actor automatically retries the whole request once more on a premium connection pool whenever the default pool is refused on every attempt, so a temporary refusal wave usually costs a few seconds, not the run. Runs that bring their own proxy connections or turn the proxy off keep exactly the connection they configured.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more AI data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🤖 <a href="https://apify.com/abotapi/suno-music-scraper"><b>Suno Scraper</b></a><br>Collect public Suno music data: songs with lyrics, style tags, model version, play and...</td><td>🧩 <a href="https://apify.com/abotapi/deezer-com-scraper"><b>Deezer Scraper</b></a><br>Scrape Deezer by keyword or URL. Extract tracks, albums, artists, playlists, lyrics...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/anghami-catalog-scraper"><b>Anghami Scraper</b></a><br>Scrape Anghami's public catalog by keyword or player URL. Rows carry their kind (song...</td><td>🧩 <a href="https://apify.com/abotapi/tidal-catalog-scraper"><b>TIDAL Scraper</b></a><br>Scrape TIDAL tracks, albums, artists, playlists and public mixes by search phrase or URL...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/universal-media-extractor"><b>Universal Media Extractor</b></a><br>Extract videos, audio, and metadata from 1000+ websites including YouTube, TikTok...</td><td>📱 <a href="https://apify.com/abotapi/google-play-reviews-scraper"><b>Google Play Reviews</b></a><br>Collect Google Play reviews and ratings for any app across country storefronts. Search by...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/udio-com-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# 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. Only the fields for the chosen mode are used; the other mode's fields are ignored.

## `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.

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

Connection settings for the run. The default Apify Proxy connection is recommended and needs a plan with Apify Proxy access.

## `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.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to exclude from UPDATED change detection. The field stays in the output; it just never makes a track UPDATED or appears in changedFields. An output column such as playCount also covers its copy inside raw. Use a dotted name such as raw.someKey to ignore a key inside a nested object or inside every element of a list. Example: playCount, if you only care about metadata changes.

## `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.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerm": "lofi",
  "sortBy": "newest",
  "trackIds": [
    "https://www.udio.com/songs/9765efef-43b4-4c0e-a3a0-ce288c99ccc7",
    "https://udio.com/songs/4b109fab-fd20-46e3-8b2e-49a3a3f57786"
  ],
  "playlistIds": [
    "https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `changes` (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",
        "https://udio.com/songs/4b109fab-fd20-46e3-8b2e-49a3a3f57786"
    ],
    "playlistIds": [
        "https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"
    ],
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": [],
    "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",
        "https://udio.com/songs/4b109fab-fd20-46e3-8b2e-49a3a3f57786",
    ],
    "playlistIds": ["https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"],
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
    "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",
    "https://udio.com/songs/4b109fab-fd20-46e3-8b2e-49a3a3f57786"
  ],
  "playlistIds": [
    "https://www.udio.com/playlists/6467edd4-f519-4418-b207-bd779b752627"
  ],
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "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/Ske1baNGZYlALEwZk/openapi.json
