# Audius Developer Docs > Audius is a fully decentralized music platform. Build on the largest open music catalog on the internet. Albums in Audius are playlists with `isAlbum` set to `true`. Use the [Playlists API](playlists) for all album operations — pass `isAlbum: true` in the metadata when creating or updating. See [createPlaylist](playlists#createplaylist) and [updatePlaylist](playlists#updateplaylist) for details. ## Getting Started with the Audius SDK ### Overview The Audius JavaScript (TypeScript) SDK allows you to easily interact with the Audius protocol. Use the SDK to: * 🔍 Search and display users, tracks, and playlists * 🎵 Stream and upload tracks * ❤️ Favorite, repost, and curate playlists * ✍️ Allow your users to [log in with their Audius account](/developers/guides/log-in-with-audius) and act on their behalf ...and much more! ### API Plans Audius offers two API plans: | Plan | Rate Limit | Monthly Requests | | ------------- | ------------------ | ---------------------- | | **Free** | 10 requests/second | 500,000 requests/month | | **Unlimited** | Unlimited | Unlimited | The Free plan is always free with no restrictions. For higher limits and support, contact [api@audius.co](mailto\:api@audius.co) about the Unlimited plan. ### Get Your API Key and Bearer Token 1. Visit the [Audius API Plans page](https://api.audius.co/plans) and click "Create API Key" to generate your credentials. 2. You will receive an **API Key** and a **Bearer Token**. Save both securely — treat them like passwords. :::tip The bearer token is the recommended way to authenticate with the Audius API. ::: ### Install the SDK * [Node.js](#nodejs) * [HTML + JS](#html--js) #### Node.js If your project is in a Node.js environment, run this in your terminal: ```bash npm install @audius/sdk ``` [@audius/sdk on NPM](https://www.npmjs.com/package/@audius/sdk) #### HTML + JS Otherwise, include the SDK script tag in your web page. The Audius SDK will then be assigned to `window.audiusSdk`. ```html ``` ### Initialize the SDK Initialize the SDK with your API key and bearer token. #### Node.js example ```js title="In Node.js environment" import { sdk } from '@audius/sdk' const audiusSdk = sdk({ apiKey: 'Your API Key goes here', bearerToken: 'Your Bearer Token goes here', }) ``` #### HTML + JS example ```js title="In web page" const audiusSdk = window.audiusSdk({ apiKey: 'Your API Key goes here', }) ``` :::warning DO NOT include the bearer token if you are running the SDK in the browser or anywhere on the client. The bearer token is what allows your app to write on behalf of the users that have authorized it to do so. Keep your bearer token secure and never expose it in client-side code that could be inspected. ::: ### Make your first API call using the SDK! Once you have the initialized SDK instance, it's smooth sailing to making your first API calls. ```js // Fetch your first track! const track = await audiusSdk.tracks.getTrack({ trackId: 'D7KyD' }) console.log(track, 'Track fetched!') // Favorite a track const userId = ( await audiusSdk.users.getUserByHandle({ handle: 'Your Audius handle goes here', }) ).data?.id await audiusSdk.tracks.favoriteTrack({ trackId: 'D7KyD', userId, }) ``` ### Full Node.js example ```js title="app.js" showLineNumbers import { sdk } from '@audius/sdk' const audiusSdk = sdk({ apiKey: 'Your API Key goes here', bearerToken: 'Your Bearer Token goes here', }) const track = await audiusSdk.tracks.getTrack({ trackId: 'D7KyD' }) console.log(track, 'Track fetched!') const userId = ( await audiusSdk.users.getUserByHandle({ handle: 'Your Audius handle goes here', }) ).data?.id await audiusSdk.tracks.favoriteTrack({ trackId: 'D7KyD', userId, }) console.log('Track favorited!') ``` ### Full HTML + JS example ```html title="index.html" showLineNumbers

Example content

``` ### What's next? * [Get authorization](/developers/guides/log-in-with-audius) to access your app's users' Audius accounts * [Explore the API docs](/sdk/tracks) to see what else you can do with the Audius SDK ### Direct API Access You can also access the Audius API directly without the SDK: **REST API:** ```bash curl -X GET "https://api.audius.co/v1/tracks/trending" \ -H "Authorization: Bearer " ``` **gRPC:** ```bash grpcurl -H "authorization: Bearer " \ grpc.audius.co:443 list ``` For more details, visit the [API documentation](https://docs.audius.co/api) or the [Swagger definition](https://api.audius.co/v1). ## OAuth Enable users to either authorize your app to perform actions on their behalf (i.e. grant write permissions), or simply verify who they are and give you access to their Audius profile info (no write permissions granted). ### Methods #### init ##### init(`params`) Enables the Audius OAuth functionality. Call this before using any other `oauth` methods. **Params** * successCallback `(profile: UserProfile, encodedJwt: string) => void` - function to be called when the user successfully authorizes or authenticates with Audius. This function will be called with the user's profile information, which is an object with the following shape: ```ts // `UserProfile` type: { userId: number; // unique Audius user identifier email: string; name: string; // user's display name handle: string; verified: boolean; // whether the user has the Audius "verified" checkmark /** URLs for the user's profile picture, if any. * If the user has a profile picture, three sizes will be available: 150x150, 480x480, and 1000x1000. * If the user has no profile picture, this field will be empty. */ profilePicture: {"150x150": string, "480x480": string, "1000x1000": string } | { misc: string } | undefined | null apiKey: string | null // the API key for your application if specified sub: number; // alias for userId iat: string; // timestamp for when auth was performed } ``` The second argument (`encodedJwt`) is for advanced use cases that want to pass the JWT to a backend for server-side verification. * errorCallback *optional* `(errorMessage: string) => void` - function to be called when an error occurs during the authentication flow. This function will be called with a string describing the error. **Returns**: Nothing **Example** ```js audiusSdk.oauth.init({ successCallback: (user) => console.log('Logged in user', user), errorCallback: (error) => console.log('Got error', error), }) ``` #### renderButton ##### renderButton(`params`) Replaces the element passed in the first parameter with the Log In with Audius button. **Params** * element `HTMLElement` - HTML element to replace with the Log In with Audius button * scope *optional* `'write' | 'read'` - Whether your app is requesting read or write permissions to the user's account. Specify "write" if you'd like the user to authorize your app to perform actions on their behalf, such as uploading a track or favoriting a playlist. Specify "read" if you simply need the user's email and profile information, and do not need any write permissions. Defaults to "read". * buttonOptions *optional* `ButtonOptions` - optional object containing the customization settings for the button to be rendered. Here are the options available: ```ts // type ButtonOptions = { // Size of the button: size?: "small | 'medium' | 'large' // Corner style of the button: corners?: 'default' | 'pill' // Your own text for the button; default is "Log In with Audius": customText?: string // Whether to disable the button's "grow" animation on hover: disableHoverGrow?: boolean // Whether the button should take up the full width of its parent element: fullWidth?: boolean } ``` Use [this playground](https://9ncjui.csb.app/) to see how these customizations affect the button appearance and determine what config works best for your app. :::note The `write` scope grants your app permission to perform most actions on the user's behalf, but it does NOT allow any access to DMs or wallets. ::: **Returns**: Nothing **Example** ```js audiusSdk.oauth.renderButton({ element: document.getElementById('audiusLogInButton'), scope: 'write', buttonOptions: { size: 'large', }, }) ``` #### login ##### login(`params`) Opens the Log In with Audius popup, which begins the authentication flow. Use this if you have your own link or button implementation, and don't want to use `renderButton`. **Params** * scope *optional* `'write' | 'read'` - Whether your app is requesting read or write permissions to the user's account. Specify "write" if you'd like the user to authorize your app to perform actions on their behalf, such as uploading a track or favoriting a playlist. Specify "read" if you simply need the user's email and profile information, and do not need any write permissions. Defaults to "read". :::note The `write` scope grants your app permission to perform most actions on the user's behalf, but it does NOT allow any access to DMs or wallets. ::: **Returns**: Nothing **Example** ```js title="script.js" function logInWithAudius() { audiusSdk.oauth.login({ scope: 'write' }) } ``` ```html title="index.html" ``` #### isWriteAccessGranted ##### isWriteAccessGranted(`params`) Checks whether your app has write permissions to the given user's account. **Params** * userId `string` - User's Audius user ID **Returns**: `Promise` **Example** ```js async function hasSkrillexAuthorizedApp() { await audiusSdk.oauth.isWriteAccessGranted({ userId: 'eAZl3' }) } ``` *** ### getPlaylist > ##### getPlaylist(`params`) Get a playlist by id. Example: ```ts const { data: playlist } = await audiusSdk.playlists.getPlaylist({ playlistId: 'D7KyD', }) console.log(playlist) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` contains a [`PlaylistResponse`](#playlistresponse) object. *** ### getBulkPlaylists > ##### getBulkPlaylists(`params`) Get a list of playlists by ID, UPC, or permalink. Example: ```ts const { data: playlists } = await audiusSdk.playlists.getBulkPlaylists({ id: ['D7KyD', '68yPZb'], }) console.log(playlists) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :---------- | :--------- | :------------------------------------ | :--------- | | `id` | `string[]` | An array of playlist IDs | *Optional* | | `permalink` | `string[]` | An array of permalinks of playlists | *Optional* | | `upc` | `string[]` | An array of UPC codes | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`PlaylistResponse`](#playlistresponse) objects. *** ### getPlaylistTracks > ##### getPlaylistTracks(`params`) Get the tracks in a playlist. Example: ```ts const { data: tracks } = await audiusSdk.playlists.getPlaylistTracks({ playlistId: 'D7KyD', }) console.log(tracks) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | ##### Returns The return type is the same as [`getBulkTracks`](tracks#getbulktracks) *** ### getTrendingPlaylists > ##### getTrendingPlaylists(`params?`) Get the top trending playlists on Audius. Example: ```ts const { data: playlists } = await audiusSdk.playlists.getTrendingPlaylists() console.log(playlists) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----- | :-------------------------------------------------------------- | :-------------------------------------------------------- | :--------- | | `time` | [`GetTrendingPlaylistsTimeEnum`](#gettrendingplayliststimeenum) | The time period for trending playlists. Default: `'week'` | *Optional* | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`PlaylistResponse`](#playlistresponse) objects. *** ### searchPlaylists > ##### searchPlaylists(`params`) Search for playlists. Example: ```ts const { data: playlists } = await audiusSdk.playlists.searchPlaylists({ query: 'skrillex', }) console.log(playlists) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------ | :------- | :---------------------- | :----------- | | `query` | `string` | The query to search for | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`PlaylistResponse`](#playlistresponse) objects. *** ### createPlaylist > ##### createPlaylist(`params`, `requestInit?`) Create a new playlist. To upload a cover image, use the [Uploads API](/sdk/uploads). Upload your image first to obtain a CID, then pass it as `playlistImageSizesMultihash` in the `metadata` object. Example: ```ts // Optional: Upload a cover image first const { start: startImageUpload } = audiusSdk.uploads.createImageUpload({ file: coverImageFile, }) const coverArtCid = await startImageUpload() const { data } = await audiusSdk.playlists.createPlaylist({ userId: '7eP5n', metadata: { playlistName: 'Summer Vibes 2024', description: 'The best summer tracks', playlistContents: [ { trackId: 'ePR5Ll', timestamp: Math.round(Date.now() / 1000) }, { trackId: 'GManBz', timestamp: Math.round(Date.now() / 1000) }, ], playlistImageSizesMultihash: coverArtCid, }, }) console.log('New playlist ID:', data.playlistId) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------- | :-------------------------------------------------------- | :-------------------- | :----------- | | `userId` | `string` | The ID of the user | **Required** | | `metadata` | [`CreatePlaylistRequestBody`](#createplaylistrequestbody) | The playlist metadata | **Required** | See [`CreatePlaylistRequestBody`](#createplaylistrequestbody) for all available metadata fields. You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { playlistId?: string transactionHash?: string } ``` *** ### updatePlaylist > ##### updatePlaylist(`params`, `requestInit?`) Update an existing playlist's metadata. If any metadata fields are not provided, their values will be kept the same as before. To replace cover art, upload a new image via the [Uploads API](/sdk/uploads) and pass the new CID in `metadata`. Example: ```ts // Optional: Upload a new cover image const { start: startImageUpload } = audiusSdk.uploads.createImageUpload({ file: newCoverImageFile, }) const coverArtCid = await startImageUpload() const { data } = await audiusSdk.playlists.updatePlaylist({ playlistId: 'D7KyD', userId: '7eP5n', metadata: { playlistName: 'Summer Vibes 2024 (Updated)', description: 'Updated playlist description', playlistContents: [ { trackId: 'ePR5Ll', timestamp: Math.round(Date.now() / 1000) }, { trackId: 'GManBz', timestamp: Math.round(Date.now() / 1000) }, { trackId: 'KWJm0x', timestamp: Math.round(Date.now() / 1000) }, ], playlistImageSizesMultihash: coverArtCid, }, }) console.log('Updated playlist:', data) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :-------------------------------------------------------- | :------------------------------ | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | | `userId` | `string` | The ID of the user | **Required** | | `metadata` | [`UpdatePlaylistRequestBody`](#updateplaylistrequestbody) | The playlist metadata to update | **Required** | All fields are optional — only include the fields you want to change. See [`UpdatePlaylistRequestBody`](#updateplaylistrequestbody) for all available fields. You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### deletePlaylist > ##### deletePlaylist(`params`, `requestInit?`) Delete a playlist. Example: ```ts await audiusSdk.playlists.deletePlaylist({ playlistId: 'D7KyD', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### favoritePlaylist > ##### favoritePlaylist(`params`, `requestInit?`) Favorite a playlist. Example: ```ts await audiusSdk.playlists.favoritePlaylist({ playlistId: 'D7KyD', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### unfavoritePlaylist > ##### unfavoritePlaylist(`params`, `requestInit?`) Unfavorite a playlist. Example: ```ts await audiusSdk.playlists.unfavoritePlaylist({ playlistId: 'D7KyD', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### repostPlaylist > ##### repostPlaylist(`params`, `requestInit?`) Repost a playlist. Example: ```ts await audiusSdk.playlists.repostPlaylist({ playlistId: 'D7KyD', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### unrepostPlaylist > ##### unrepostPlaylist(`params`, `requestInit?`) Unrepost a playlist. Example: ```ts await audiusSdk.playlists.unrepostPlaylist({ playlistId: 'D7KyD', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------- | :--------------------- | :----------- | | `playlistId` | `string` | The ID of the playlist | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### Type Reference #### PlaylistResponse | Field | Type | Description | | :--------------------- | :--------- | :------------------------------------------------------------- | | `artwork` | `object` | Artwork images (see below) | | `artwork._1000x1000` | `string` | URL of 1000x1000 artwork | | `artwork._150x150` | `string` | URL of 150x150 artwork | | `artwork._480x480` | `string` | URL of 480x480 artwork | | `coverArtSizes` | `string` | CID of the cover art sizes | | `description` | `string` | Playlist description | | `favoriteCount` | `number` | Number of favorites | | `id` | `string` | Playlist ID | | `isAlbum` | `boolean` | Whether this is an album | | `isImageAutogenerated` | `boolean` | Whether the cover art is auto-generated | | `isPrivate` | `boolean` | Whether the playlist is private | | `permalink` | `string` | Permalink URL | | `playlistContents` | `object[]` | Array of track entries (trackId, timestamp, metadataTimestamp) | | `playlistName` | `string` | Name of the playlist | | `repostCount` | `number` | Number of reposts | | `totalPlayCount` | `number` | Total play count | | `user` | `object` | The playlist owner (see [User](users)) | #### GetTrendingPlaylistsTimeEnum | Value | String | Description | | :-------- | :---------- | :------------------ | | `Week` | `'week'` | Past week (default) | | `Month` | `'month'` | Past month | | `Year` | `'year'` | Past year | | `AllTime` | `'allTime'` | All time | #### CreatePlaylistRequestBody | Field | Type | Required | Description | | ----------------------------- | ------------------------------------------------------------- | -------- | ----------------------------------------------------- | | `playlistName` | `string` | Yes | The name of the playlist | | `playlistId` | `string` | No | Optional playlist ID (auto-generated if not provided) | | `playlistImageSizesMultihash` | `string` | No | CID of the cover art image (from Uploads API) | | `description` | `string` | No | The playlist description | | `isAlbum` | `boolean` | No | Whether this is an album | | `isPrivate` | `boolean` | No | Whether the playlist is private | | `genre` | [`Genre`](tracks#genre-values) | No | Playlist/album genre | | `mood` | [`Mood`](tracks#mood-values) | No | Playlist/album mood | | `tags` | `string` | No | Comma-separated tags | | `license` | `string` | No | License type | | `upc` | `string` | No | Universal Product Code (for albums) | | `releaseDate` | `Date` | No | Release date | | `playlistContents` | [`PlaylistAddedTimestamp[]`](#playlistaddedtimestamp) | No | Array of tracks to include in the playlist | | `isStreamGated` | `boolean` | No | Whether streaming is behind an access gate | | `isScheduledRelease` | `boolean` | No | Whether this is a scheduled release | | `streamConditions` | [`AccessGate`](tracks#accessgate) | No | Conditions for stream access gating | | `ddexApp` | `string` | No | DDEX application identifier | | `ddexReleaseIds` | `object` | No | DDEX release identifiers | | `artists` | [`DdexResourceContributor[]`](tracks#ddexresourcecontributor) | No | DDEX resource contributors / artists | | `copyrightLine` | `object` | No | Copyright line | | `producerCopyrightLine` | `object` | No | Producer copyright line | | `parentalWarningType` | `string` | No | Parental warning type | | `isImageAutogenerated` | `boolean` | No | Whether the image is autogenerated | #### UpdatePlaylistRequestBody All fields are optional — only include the fields you want to change. | Field | Type | Required | Description | | ----------------------------- | ------------------------------------------------------------- | -------- | ---------------------------------------------- | | `playlistName` | `string` | No | The name of the playlist | | `playlistImageSizesMultihash` | `string` | No | CID of the cover art image (from Uploads API) | | `description` | `string` | No | The playlist description | | `isAlbum` | `boolean` | No | Whether this is an album | | `isPrivate` | `boolean` | No | Whether the playlist is private | | `genre` | [`Genre`](tracks#genre-values) | No | Playlist/album genre | | `mood` | [`Mood`](tracks#mood-values) | No | Playlist/album mood | | `tags` | `string` | No | Comma-separated tags | | `license` | `string` | No | License type | | `upc` | `string` | No | Universal Product Code (for albums) | | `releaseDate` | `Date` | No | Release date | | `playlistContents` | [`PlaylistAddedTimestamp[]`](#playlistaddedtimestamp) | No | Array of tracks in the playlist (replaces all) | | `isStreamGated` | `boolean` | No | Whether streaming is behind an access gate | | `isScheduledRelease` | `boolean` | No | Whether this is a scheduled release | | `streamConditions` | [`AccessGate`](tracks#accessgate) | No | Conditions for stream access gating | | `ddexApp` | `string` | No | DDEX application identifier | | `ddexReleaseIds` | `object` | No | DDEX release identifiers | | `artists` | [`DdexResourceContributor[]`](tracks#ddexresourcecontributor) | No | DDEX resource contributors / artists | | `copyrightLine` | `object` | No | Copyright line | | `producerCopyrightLine` | `object` | No | Producer copyright line | | `parentalWarningType` | `string` | No | Parental warning type | | `isImageAutogenerated` | `boolean` | No | Whether the image is autogenerated | #### PlaylistAddedTimestamp Represents a track entry within a playlist. | Field | Type | Required | Description | | ------------------- | -------- | -------- | -------------------------------------------- | | `trackId` | `string` | Yes | The ID of the track | | `timestamp` | `number` | Yes | Timestamp when the track was added (seconds) | | `metadataTimestamp` | `number` | No | Optional metadata timestamp (seconds) | import { Tabs } from '../../components/Tabs.jsx' import { TabItem } from '../../components/Tabs.jsx' ## Progress Events Some methods have optional `onProgress` callbacks to handle progress events for uploading resources. The first argument to the `onProgress` callback is always a rolled up progress value from 0-1. The second will be one of the following types depending on the method. ### UploadTrackProgressEvent ```ts { key: 'audio' | 'image' // Which file upload triggered the event loaded: number // How much of the file has been uploaded, in bytes total: number // Total number of bytes of the file transcode: number // Completion percent (from 0-1) of the transcoding } ``` ### UploadPlaylistProgressEvent ```ts { key: 'image' | number // The index of the audioFile or 'image' loaded: number // How much of the file has been uploaded, in bytes total: number // Total number of bytes of the file transcode: number // Completion percent (from 0-1) of the transcoding } ``` import { Tabs } from '../../components/Tabs.jsx' import { TabItem } from '../../components/Tabs.jsx' #### resolve ##### resolve(`params`) Resolve a provided Audius app URL to the API resource it represents. Example: ```ts const { data: track } = await audiusSdk.resolve({ url: 'https://audius.co/camouflybeats/hypermantra-86216', }) console.log(track) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :---- | :------- | :--------------------------------- | :----------- | | `url` | `string` | The url of the resource to resolve | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an object representing the resolved resource as described below. ```ts { data: Track | Playlist | Album } ``` *** *** ### getTrack > ##### getTrack(`params`, `requestInit?`) Get a track by id. Example: ```ts const { data: track } = await audiusSdk.tracks.getTrack({ trackId: 'D7KyD', }) console.log(track) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------- | :------- | :------------------------------------ | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user making the request | *Optional* | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with a `data` field. `data` contains a [`TrackResponse`](#trackresponse) object. *** ### getBulkTracks > ##### getBulkTracks(`params`, `requestInit?`) Get a list of tracks using their IDs or permalinks. Example: ```ts const { data: tracks } = await audiusSdk.tracks.getBulkTracks({ id: ['D7KyD', 'PjdWN', 'Jwo2A'], }) console.log(tracks) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :---------- | :--------- | :---------------------------------------- | :--------- | | `id` | `string[]` | An array of IDs of tracks | *Optional* | | `permalink` | `string[]` | An array of permalinks of tracks | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | | `isrc` | `string[]` | An array of ISRC codes to query tracks by | *Optional* | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`TrackResponse`](#trackresponse) objects. *** ### getTrendingTracks > ##### getTrendingTracks(`params`, `requestInit?`) Get the top 100 trending (most popular) tracks on Audius. Example: ```ts const { data: tracks } = await audiusSdk.tracks.getTrendingTracks() console.log(tracks) ``` ##### Params Optionally create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :----------------------------------------- | :--------------------------------------------------------------------------------- | :--------- | | `genre` | [`Genre`](#genre-values) | If provided, the top 100 trending tracks of the genre will be returned | *Optional* | | `time` | `'week' \| 'month' \| 'year' \| 'allTime'` | A time range for which to return the trending tracks. Default value is `'allTime'` | *Optional* | | `offset` | `number` | The number of items to skip (for pagination) | *Optional* | | `limit` | `number` | The number of items to fetch (for pagination) | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`TrackResponse`](#trackresponse) objects. *** ### getUndergroundTrendingTracks > ##### getUndergroundTrendingTracks(`params`, `requestInit?`) Get the top 100 trending underground tracks on Audius. Example: ```ts const { data: tracks } = await audiusSdk.tracks.getUndergroundTrendingTracks() console.log(tracks) ``` ##### Params Optionally create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :--------------------------------------------------------------------------- | :--------- | | `limit` | `number` | If provided, will return only the given number of tracks. Default is **100** | *Optional* | | `offset` | `number` | An offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`TrackResponse`](#trackresponse) objects. *** ### searchTracks > ##### searchTracks(`params`, `requestInit?`) Search for tracks. Example: ```ts const { data: tracks } = await audiusSdk.tracks.searchTracks({ query: 'skrillex', }) console.log(tracks) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------------------- | :------------------------- | :------------------------------------------------------- | :--------- | | `query` | `string` | The query to search for | *Optional* | | `offset` | `number` | The number of items to skip (for pagination) | *Optional* | | `limit` | `number` | The number of items to fetch (for pagination) | *Optional* | | `genre` | [`Genre[]`](#genre-values) | Array of genres to filter by | *Optional* | | `sortMethod` | `string` | Sort method: `'relevant'`, `'popular'`, or `'recent'` | *Optional* | | `mood` | [`Mood[]`](#mood-values) | Array of moods to filter by | *Optional* | | `onlyDownloadable` | `string` | Filter to only downloadable tracks | *Optional* | | `includePurchaseable` | `string` | Include purchaseable tracks in results | *Optional* | | `isPurchaseable` | `string` | Filter to only purchaseable tracks | *Optional* | | `hasDownloads` | `string` | Filter tracks that have stems/downloads | *Optional* | | `key` | `string[]` | Array of musical keys to filter by (e.g., `['C', 'Am']`) | *Optional* | | `bpmMin` | `string` | Minimum BPM to filter by | *Optional* | | `bpmMax` | `string` | Maximum BPM to filter by | *Optional* | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of [`TrackResponse`](#trackresponse) objects. *** ### createTrack > ##### createTrack(`params`, `requestInit?`) Create a track by registering its metadata on the protocol. This method accepts metadata including CIDs (Content Identifiers) that reference previously uploaded files — it does **not** accept raw audio or image files directly. :::info[Uploading files] To upload audio and image files, use the [Uploads API](/sdk/uploads). Upload your files first to obtain CIDs, then pass those CIDs in the `metadata` object here. See the [full upload + create example](/sdk/uploads#full-example-upload-and-create-a-track) for a complete walkthrough. ::: Example: ```ts import fs from 'fs' // First, upload files using the Uploads API const trackBuffer = fs.readFileSync('path/to/track.mp3') const audioUpload = audiusSdk.uploads.createAudioUpload({ file: { buffer: Buffer.from(trackBuffer), name: 'track.mp3', type: 'audio/mpeg', }, }) const audioResult = await audioUpload.start() const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') const imageUpload = audiusSdk.uploads.createImageUpload({ file: { buffer: Buffer.from(coverArtBuffer), name: 'cover-art.png', type: 'image/png', }, }) const coverArtCid = await imageUpload.start() // Then, create the track with the returned CIDs. // The audio upload result fields (trackCid, origFileCid, etc.) match // the metadata field names, so you can spread them directly. const { data } = await audiusSdk.tracks.createTrack({ userId: '7eP5n', metadata: { title: 'Monstera', description: 'Dedicated to my favorite plant', genre: 'Metal', mood: 'Aggressive', ...audioResult, coverArtSizes: coverArtCid, }, }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------- | :-------------------------------------------------- | :-------------------------------------------- | :----------- | | `userId` | `string` | The ID of the user | **Required** | | `metadata` | [`CreateTrackRequestBody`](#createtrackrequestbody) | An object containing the details of the track | **Required** | See [`CreateTrackRequestBody`](#createtrackrequestbody) for all available metadata fields. You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ````ts { transactionHash?: string trackId?: string } --- ## updateTrack > #### updateTrack(`params`, `requestInit?`) Update a track's metadata. If any metadata fields are not provided, their values will be kept the same as before. To replace audio or cover art, upload new files via the [Uploads API](/sdk/uploads) and pass the new CIDs in `metadata`. Example: ```ts const { data } = await audiusSdk.tracks.updateTrack({ trackId: 'h5pJ3Bz', userId: '7eP5n', metadata: { description: 'Dedicated to my favorite plant... updated!', mood: 'Yearning', }, }) ```` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------- | :-------------------------------------------------- | :---------------------------------------- | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | | `metadata` | [`UpdateTrackRequestBody`](#updatetrackrequestbody) | An object containing the fields to update | **Required** | All fields are optional — only include the fields you want to change. See [`UpdateTrackRequestBody`](#updatetrackrequestbody) for all available fields. You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### deleteTrack > ##### deleteTrack(`params`, `requestInit?`) Delete a track Example: ```ts await audiusSdk.tracks.deleteTrack({ trackId: 'h5pJ3Bz', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------- | :------- | :------------------ | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### favoriteTrack > ##### favoriteTrack(`params`, `requestInit?`) Favorite a track Example: ```ts await audiusSdk.tracks.favoriteTrack({ trackId: 'x5pJ3Az' userId: "7eP5n", }); ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------- | :--------------------- | :------------------------------------------------------------------- | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | | `metadata` | *see code block below* | Set `isSaveOfRepost` to true if you are favoriting a reposted track. | *Optional* | ```json title="favoriteTrack metadata payload" { isSaveOfRepost: boolean } ``` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### unfavoriteTrack > ##### unfavoriteTrack(`params`, `requestInit?`) Unfavorite a track Example: ```ts await audiusSdk.tracks.unfavoriteTrack({ trackId: 'x5pJ3Az' userId: "7eP5n", }); ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------- | :------- | :------------------ | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### repostTrack > ##### repostTrack(`params`, `requestInit?`) Repost a track Example: ```ts await audiusSdk.tracks.repostTrack({ trackId: 'x5pJ3Az' userId: "7eP5n", }); ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------- | :--------------------- | :-------------------------------------------------------------------- | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | | `metadata` | *see code block below* | Set `isRepostOfRepost` to true if you are reposting a reposted track. | *Optional* | ```json title="repostTrack metadata payload" { isRepostOfRepost: boolean } ``` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### unrepostTrack > ##### unrepostTrack(`params`, `requestInit?`) Unrepost a track Example: ```ts await audiusSdk.tracks.unrepostTrack({ trackId: 'x5pJ3Az', userId: '7eP5n', }) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------- | :------- | :------------------ | :----------- | | `trackId` | `string` | The ID of the track | **Required** | | `userId` | `string` | The ID of the user | **Required** | You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` resolving to an object with the following fields: ```ts { transactionHash?: string } ``` *** ### Type Reference #### TrackResponse | Field | Type | Description | | :------------------- | :----------------------- | :-------------------------------------------------------- | | `artwork` | `object` | Artwork images (see below) | | `artwork._1000x1000` | `string` | URL of 1000x1000 artwork | | `artwork._150x150` | `string` | URL of 150x150 artwork | | `artwork._480x480` | `string` | URL of 480x480 artwork | | `description` | `string` | Track description | | `downloadable` | `boolean` | Whether the track is downloadable | | `duration` | `number` | Track duration in seconds | | `favoriteCount` | `number` | Number of favorites | | `genre` | [`Genre`](#genre-values) | Track genre | | `id` | `string` | Track ID | | `isStreamable` | `string` | Whether the track is streamable | | `mood` | [`Mood`](#mood-values) | Track mood | | `permalink` | `string` | Permalink URL | | `playCount` | `number` | Number of plays | | `releaseDate` | `string` | Release date | | `remixOf` | `object` | Remix parent info (`tracks: { parentTrackId: string }[]`) | | `repostCount` | `number` | Number of reposts | | `tags` | `string[]` | Array of tags | | `title` | `string` | Track title | | `trackCid` | `string` | CID of the track audio | | `user` | `object` | The track owner (see [User](users)) | *** #### CreateTrackRequestBody Metadata object for creating a new track. Fields marked **Required** must be provided. | Name | Type | Description | Required? | | :----------------------------- | :------------------------------------------------------ | :------------------------------------------------ | :----------- | | `title` | `string` | Track title | **Required** | | `genre` | [`Genre`](#genre-values) | Track genre | **Required** | | `trackCid` | `string` | CID of the transcoded audio (from Uploads API) | **Required** | | `trackId` | `string` | Track ID (auto-generated if not provided) | *Optional* | | `description` | `string` | Track description | *Optional* | | `mood` | [`Mood`](#mood-values) | Track mood | *Optional* | | `bpm` | `number` | Beats per minute | *Optional* | | `musicalKey` | `string` | Musical key of the track | *Optional* | | `tags` | `string` | Comma-separated tags | *Optional* | | `license` | `string` | License type | *Optional* | | `isrc` | `string` | International Standard Recording Code | *Optional* | | `iswc` | `string` | International Standard Musical Work Code | *Optional* | | `releaseDate` | `Date` | Release date | *Optional* | | `origFileCid` | `string` | CID of the original audio file (from Uploads API) | *Optional* | | `origFilename` | `string` | Original filename of the audio file | *Optional* | | `coverArtSizes` | `string` | CID of the cover art image (from Uploads API) | *Optional* | | `previewCid` | `string` | CID of the preview clip (from Uploads API) | *Optional* | | `previewStartSeconds` | `number` | Preview start time in seconds | *Optional* | | `duration` | `number` | Track duration in seconds | *Optional* | | `isDownloadable` | `boolean` | Whether the track is downloadable | *Optional* | | `isUnlisted` | `boolean` | Whether the track is unlisted (hidden) | *Optional* | | `isStreamGated` | `boolean` | Whether streaming is behind an access gate | *Optional* | | `streamConditions` | [`AccessGate`](#accessgate) | Conditions for stream access gating | *Optional* | | `downloadConditions` | [`AccessGate`](#accessgate) | Conditions for download access gating | *Optional* | | `fieldVisibility` | [`FieldVisibility`](#fieldvisibility) | Controls visibility of individual track fields | *Optional* | | `placementHosts` | `string` | Preferred storage node placement hosts | *Optional* | | `stemOf` | [`StemParent`](#stemparent) | Parent track if this track is a stem | *Optional* | | `remixOf` | [`RemixParentWrite`](#remixparentwrite) | Parent track(s) if this track is a remix | *Optional* | | `noAiUse` | `boolean` | Whether AI use is prohibited | *Optional* | | `isOwnedByUser` | `boolean` | Whether the track is owned by the user | *Optional* | | `coverOriginalSongTitle` | `string` | Original song title (for cover tracks) | *Optional* | | `coverOriginalArtist` | `string` | Original artist (for cover tracks) | *Optional* | | `parentalWarningType` | `string` | Parental warning type | *Optional* | | `territoryCodes` | `string[]` | Territory codes for distribution | *Optional* | | `ddexApp` | `string` | DDEX application identifier | *Optional* | | `ddexReleaseIds` | `object` | DDEX release identifiers | *Optional* | | `artists` | `object[]` | DDEX resource contributors / artists | *Optional* | | `resourceContributors` | [`DdexResourceContributor[]`](#ddexresourcecontributor) | DDEX resource contributors | *Optional* | | `indirectResourceContributors` | [`DdexResourceContributor[]`](#ddexresourcecontributor) | DDEX indirect resource contributors | *Optional* | | `rightsController` | [`DdexRightsController`](#ddexrightscontroller) | DDEX rights controller | *Optional* | | `copyrightLine` | `CopyrightLine` | Copyright line | *Optional* | | `producerCopyrightLine` | `ProducerCopyrightLine` | Producer copyright line | *Optional* | *** #### UpdateTrackRequestBody Metadata object for updating an existing track. All fields are optional — only include the fields you want to change. Omitted fields retain their current values. | Name | Type | Description | | :-------------------- | :-------------------------------------- | :------------------------------------------------ | | `title` | `string` | Track title | | `genre` | [`Genre`](#genre-values) | Track genre | | `description` | `string` | Track description | | `mood` | [`Mood`](#mood-values) | Track mood | | `bpm` | `number` | Beats per minute | | `musicalKey` | `string` | Musical key of the track | | `tags` | `string` | Comma-separated tags | | `license` | `string` | License type | | `isrc` | `string` | International Standard Recording Code | | `iswc` | `string` | International Standard Musical Work Code | | `releaseDate` | `Date` | Release date | | `trackCid` | `string` | CID of the transcoded audio (from Uploads API) | | `origFileCid` | `string` | CID of the original audio file (from Uploads API) | | `origFilename` | `string` | Original filename of the audio file | | `coverArtSizes` | `string` | CID of the cover art image (from Uploads API) | | `previewCid` | `string` | CID of the preview clip (from Uploads API) | | `previewStartSeconds` | `number` | Preview start time in seconds | | `duration` | `number` | Track duration in seconds | | `isDownloadable` | `boolean` | Whether the track is downloadable | | `isUnlisted` | `boolean` | Whether the track is unlisted (hidden) | | `isStreamGated` | `boolean` | Whether streaming is behind an access gate | | `streamConditions` | [`AccessGate`](#accessgate) | Conditions for stream access gating | | `downloadConditions` | [`AccessGate`](#accessgate) | Conditions for download access gating | | `fieldVisibility` | [`FieldVisibility`](#fieldvisibility) | Controls visibility of individual track fields | | `placementHosts` | `string` | Preferred storage node placement hosts | | `stemOf` | [`StemParent`](#stemparent) | Parent track if this track is a stem | | `remixOf` | [`RemixParentWrite`](#remixparentwrite) | Parent track(s) if this track is a remix | | `parentalWarningType` | `string` | Parental warning type | | `ddexApp` | `string` | DDEX application identifier | *** #### AccessGate `AccessGate` is a union type representing the conditions required to access a gated track. Used by both `streamConditions` and `downloadConditions`. Provide **one** of the following: ##### FollowGate Require the listener to follow a specific user. ```ts { followUserId: number // The user ID that must be followed } ``` ##### TipGate Require the listener to have tipped a specific user. ```ts { tipUserId: number // The user ID that must be tipped } ``` ##### PurchaseGate Require the listener to purchase access with USDC. ```ts { usdcPurchase: { price: number // Price in cents splits: Array<{ userId: number // User ID of the payment recipient percentage: number // Percentage of the price (0-100) }> } } ``` ##### TokenGate Require the listener to hold a specific SPL token. ```ts { tokenGate: { tokenMint: string // The mint address of the SPL token tokenAmount: number // The minimum amount of the token required } } ``` **Example — USDC purchase-gated track:** ```ts const metadata = { title: 'Premium Track', genre: 'Electronic', trackCid: '...', isStreamGated: true, streamConditions: { usdcPurchase: { price: 199, // $1.99 in cents splits: [{ userId: 12345, percentage: 100 }], }, }, } ``` *** #### FieldVisibility Controls which track fields are publicly visible. Each field is a `boolean`. ```ts { mood: boolean tags: boolean genre: boolean share: boolean playCount: boolean remixes: boolean } ``` :::note For public tracks, `genre`, `mood`, `tags`, `share`, and `playCount` are set to `true` by default. For stream-gated (non-USDC) tracks, `remixes` is set to `false` by default. ::: *** #### RemixParentWrite Identifies the parent track(s) that a remix is derived from. ```ts { tracks: Array<{ parentTrackId: string // The ID of the parent track }> } ``` **Example:** ```ts const metadata = { title: 'My Remix', genre: 'Electronic', trackCid: '...', remixOf: { tracks: [{ parentTrackId: 'D7KyD' }], }, } ``` *** #### StemParent Identifies the parent track when uploading a stem (e.g. vocals, drums, bass). ```ts { category: string // Stem category (e.g. 'VOCAL', 'DRUMS', 'BASS', 'INSTRUMENTAL', 'OTHER') parentTrackId: number // The numeric ID of the parent track } ``` *** #### DdexResourceContributor Represents a contributor to the track (used for DDEX metadata). ```ts { name: string // Contributor name roles: string[] // Contributor roles (e.g. 'Composer', 'Lyricist') sequenceNumber?: number // Optional ordering index } ``` *** #### DdexRightsController Represents the rights controller for the track (used for DDEX metadata). ```ts { name: string // Rights controller name roles: string[] // Roles (e.g. 'RightsController') rightsShareUnknown?: string // Optional rights share info } ``` *** #### Genre Values Valid genre values for the `genre` field. Use these exact string values: `'Electronic'`, `'Rock'`, `'Metal'`, `'Alternative'`, `'Hip-Hop/Rap'`, `'Experimental'`, `'Punk'`, `'Folk'`, `'Pop'`, `'Ambient'`, `'Soundtrack'`, `'World'`, `'Jazz'`, `'Acoustic'`, `'Funk'`, `'R&B/Soul'`, `'Devotional'`, `'Classical'`, `'Reggae'`, `'Podcasts'`, `'Country'`, `'Spoken Word'`, `'Comedy'`, `'Blues'`, `'Kids'`, `'Audiobooks'`, `'Latin'`, `'Lo-Fi'`, `'Hyperpop'`, `'Dancehall'`, `'Techno'`, `'Trap'`, `'House'`, `'Tech House'`, `'Deep House'`, `'Disco'`, `'Electro'`, `'Jungle'`, `'Progressive House'`, `'Hardstyle'`, `'Glitch Hop'`, `'Trance'`, `'Future Bass'`, `'Future House'`, `'Tropical House'`, `'Downtempo'`, `'Drum & Bass'`, `'Dubstep'`, `'Jersey Club'`, `'Vaporwave'`, `'Moombahton'` *** #### Mood Values Valid mood values for the `mood` field. Use these exact string values: `'Peaceful'`, `'Romantic'`, `'Sentimental'`, `'Tender'`, `'Easygoing'`, `'Yearning'`, `'Sophisticated'`, `'Sensual'`, `'Cool'`, `'Gritty'`, `'Melancholy'`, `'Serious'`, `'Brooding'`, `'Fiery'`, `'Defiant'`, `'Aggressive'`, `'Rowdy'`, `'Excited'`, `'Energizing'`, `'Empowering'`, `'Stirring'`, `'Upbeat'`, `'Other'` import { Tabs } from '../../components/Tabs.jsx' import { TabItem } from '../../components/Tabs.jsx' *** The Uploads API provides methods for uploading audio and image files to Audius storage nodes. These methods return CIDs (Content Identifiers) that you then pass to write methods like [`createTrack`](/sdk/tracks#createtrack) or [`updateTrack`](/sdk/tracks#updatetrack). :::tip File uploads are a separate step from track/playlist creation. You first upload your files using the Uploads API to get CIDs, then pass those CIDs as metadata when creating or updating content. ::: *** ### createAudioUpload > ##### createAudioUpload(`params`) Upload an audio file to a storage node. Returns the resulting CIDs and audio analysis metadata (duration, BPM, musical key). Create an object with the following fields and pass it as the first argument. | Name | Type | Description | Required? | | :-------------------- | :---------------------------------------- | :--------------------------------------------------- | :----------- | | `file` | `File` | The audio file to upload | **Required** | | `onProgress` | `(loaded: number, total: number) => void` | A callback for tracking upload progress | *Optional* | | `previewStartSeconds` | `number` | Start time in seconds for generating a preview clip | *Optional* | | `placementHosts` | `string[]` | A list of storage node hosts to prefer for placement | *Optional* | ```ts import fs from 'fs' const trackBuffer = fs.readFileSync('path/to/track.mp3') const upload = audiusSdk.uploads.createAudioUpload({ file: { buffer: Buffer.from(trackBuffer), name: 'track.mp3', type: 'audio/mpeg', }, onProgress: (loaded, total) => { console.log(`Upload progress: ${Math.round((loaded / total) * 100)}%`) }, previewStartSeconds: 30, }) const result = await upload.start() console.log(result) ``` The method returns an object with: * `abort` — a function you can call to cancel the upload. * `start()` — a function that begins the upload and returns a `Promise` that resolves with the upload result once complete. The resolved value of `start()` contains: ```ts { trackCid: string // CID of the transcoded 320kbps audio previewCid?: string // CID of the preview clip (if previewStartSeconds was provided) origFileCid: string // CID of the original uploaded file origFilename: string // Original filename duration: number // Duration in seconds bpm?: number // Detected BPM (beats per minute) musicalKey?: string // Detected musical key } ``` *** ### createImageUpload > ##### createImageUpload(`params`) Upload an image file (e.g. cover art or profile picture) to a storage node. Returns the resulting CID. Create an object with the following fields and pass it as the first argument. | Name | Type | Description | Required? | | :--------------- | :---------------------------------------- | :---------------------------------------------------------------------------- | :----------- | | `file` | `File` | The image file to upload | **Required** | | `onProgress` | `(loaded: number, total: number) => void` | A callback for tracking upload progress | *Optional* | | `isBackdrop` | `boolean` | Set to `true` for wide/banner images (e.g. profile banners). Default: `false` | *Optional* | | `placementHosts` | `string[]` | A list of storage node hosts to prefer for placement | *Optional* | ```ts import fs from 'fs' const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') const upload = audiusSdk.uploads.createImageUpload({ file: { buffer: Buffer.from(coverArtBuffer), name: 'cover-art.png', type: 'image/png', }, }) const coverArtCid = await upload.start() console.log(coverArtCid) // CID string ``` The method returns an object with: * `abort` — a function you can call to cancel the upload. * `start()` — a function that begins the upload and returns a `Promise` resolving with the CID of the uploaded image (`string`). *** ### Full Example: Upload and Create a Track This example demonstrates the full workflow of uploading files via the Uploads API and then creating a track with the returned CIDs. ```ts import fs from 'fs' // Step 1: Upload the audio file const trackBuffer = fs.readFileSync('path/to/track.mp3') const audioUpload = audiusSdk.uploads.createAudioUpload({ file: { buffer: Buffer.from(trackBuffer), name: 'track.mp3', type: 'audio/mpeg', }, previewStartSeconds: 30, }) const audioResult = await audioUpload.start() // Step 2: Upload cover art const coverArtBuffer = fs.readFileSync('path/to/cover-art.png') const imageUpload = audiusSdk.uploads.createImageUpload({ file: { buffer: Buffer.from(coverArtBuffer), name: 'cover-art.png', type: 'image/png', }, }) const coverArtCid = await imageUpload.start() // Step 3: Create the track using the CIDs from the uploads. // The audio upload result fields (trackCid, previewCid, origFileCid, etc.) // match the metadata field names, so you can spread them directly. const { data } = await audiusSdk.tracks.createTrack({ userId: '7eP5n', metadata: { title: 'Monstera', description: 'Dedicated to my favorite plant', genre: 'Metal', mood: 'Aggressive', ...audioResult, coverArtSizes: coverArtCid, }, }) ``` import { Tabs } from '../../components/Tabs.jsx' import { TabItem } from '../../components/Tabs.jsx' #### getUser ##### getUser(`params`) Get a user. Example: ```ts const { data: user } = await audiusSdk.users.getUser({ id: 'eAZl3', }) console.log(user) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--- | :------- | :----------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` contains information about the user as described below. ```ts { albumCount: number; artistPickTrackId?: string; bio?: string; coverPhoto?: { _2000?: string; _640?: string; }; doesFollowCurrentUser?: boolean; ercWallet: string; followeeCount: number; followerCount: number; handle: string; id: string; isAvailable: boolean; isDeactivated: boolean; isVerified: boolean; location?: string; name: string; playlistCount: number; profilePicture?: { _1000x1000?: string; _150x150?: string; _480x480?: string; }; repostCount: number; splWallet: string; supporterCount: number; supportingCount: number; totalAudioBalance: number; trackCount: number; }; ``` *** #### getBulkUsers ##### getBulkUsers(`params`) Gets a list of users. Example: ```ts const { data: users } = await audiusSdk.users.getBulkUsers({ id: ['eAZl3', 'v7O9O'], }) console.log(users) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :--------- | :------------------------------------ | :--------- | | `id` | `string[]` | The IDs of the users | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns Returns an array of users, see [`getUser`](#getuser). *** #### getAIAttributedTracksByUserHandle ##### getAIAttributedTracksByUserHandle(`params`) Get the AI generated tracks attributed to a user using the user's handle. Example: ```ts const { data: tracks } = await audiusSdk.users.getAIAttributedTracksByUserHandle({ handle: 'skrillex', }) console.log(tracks) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------------- | :------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :----------- | | `handle` | `string` | The handle of the user | **Required** | | `limit` | `number` | The maximum number of tracks to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `filterTracks` | `GetAIAttributedTracksByUserHandleFilterTracksEnum` (can be imported from `@audius/sdk`) | A filter to apply to the returned tracks. Default value is **GetAIAttributableTracksByUserHandleFilterTracksEnum.All** | *Optional* | | `query` | `string` | A query to search for in a user's tracks | *Optional* | | `sortDirection` | `GetAIAttributedTracksByUserHandleSortDirectionEnum` (can be imported from `@audius/sdk`) | A sort direction to apply to the returned tracks. Default value is **GetAIAttributableTracksByUserHandleSortDirectionEnum.Asc** | *Optional* | | `sortMethod` | `GetAIAttributedTracksByUserTracksByUserHandleSortMethodEnum` (can be imported from `@audius/sdk`) | A sort method to apply to the returned tracks | *Optional* | ##### Returns The return type is the same as [`getBulkTracks`](tracks#getbulktracks) *** #### getAuthorizedApps ##### getAuthorizedApps(`params`) Get the apps that a user has authorized to write to their account. Example: ```ts const { data: apps } = await audiusSdk.users.getAuthorizedApps({ id: 'eAZl3', }) console.log(apps) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--- | :------- | :----------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing information about the authorized apps as described below. ```ts { address: string; description?: string; grantCreatedAt: string; grantUpdatedAt: string; grantorUserId: string; name: string; }[]; ``` *** #### getConnectedWallets ##### getConnectedWallets(`params`) Get a user's connected ERC and SPL wallets. Example: ```ts const { data: wallets } = await audiusSdk.users.getConnectedWallets({ id: 'eAZl3', }) console.log(wallets) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--- | :------- | :----------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an object containing information about the wallets as described below. ```ts { ercWallets: string[]; splWallets: string[]; }; ``` *** #### getFavorites ##### getFavorites(`params`) Get a user's favorites. Example: ```ts const { data: favorites } = await audiusSdk.users.getFavorites({ id: 'eAZl3', }) console.log(favorites) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--- | :------- | :----------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing information about the favorites as described below. ```ts { createdAt: string favoriteItemId: string // The ID of the track, playlist, or album favoriteType: string // The type of favorite ("track", "playlist", or "album") userId: string } ;[] ``` *** #### getFollowers ##### getFollowers(`params`) Get a user's followers Example: ```ts const { data: followers } = await audiusSdk.users.getFollowers({ id: 'eAZl3', }) console.log(followers) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of followers to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing information about the followers as described below. ```ts { albumCount: number; artistPickTrackId?: string; bio?: string; coverPhoto?: { _2000?: string; _640?: string; }; doesFollowCurrentUser?: boolean; ercWallet: string; followeeCount: number; followerCount: number; handle: string; id: string; isAvailable: boolean; isDeactivated: boolean; isVerified: boolean; location?: string; name: string; playlistCount: number; profilePicture?: { _1000x1000?: string; _150x150?: string; _480x480?: string; }; repostCount: number; splWallet: string; supporterCount: number; supportingCount: number; totalAudioBalance: number; trackCount: number; }[]; ``` *** #### getFollowing ##### getFollowing(`params`) Get users that a user is following Example: ```ts const { data: following } = await audiusSdk.users.getFollowing({ id: 'eAZl3', }) console.log(following) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns The return type is the same as [`getFollowers`](#getfollowers) *** #### getRelatedUsers ##### getRelatedUsers(`params`) Get a list of users that might be of interest to followers of this user. Example: ```ts const { data: relatedUsers } = await audiusSdk.users.getRelatedUsers({ id: 'eAZl3', }) console.log(relatedUsers) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------------- | :-------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | | `filterFollowed` | `boolean` | Filter to only users that the current user follows | *Optional* | ##### Returns The return type is the same as [`getFollowers`](#getfollowers) *** ##### getReposts(`params`) Get a user's reposts. Example: ```ts const { data: reposts } = await audiusSdk.users.getReposts({ id: 'eAZl3', }) console.log(reposts) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of reposts to return. Default value is **100** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing information about the reposts as described below. Return type: ```ts { item?: { id: string; }; // The entire item is returned, always contains id itemType?: string; // The type of the item ("track", "playlist", or "album") timestamp?: string; }[]; ``` *** #### getSubscribers ##### getSubscribers(`params`) Get users that are subscribed to a user. Example: ```ts const { data: subscribers } = await audiusSdk.users.getSubscribers({ id: 'eAZl3', }) console.log(subscribers) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns The return type is the same as [`getFollowers`](#getfollowers) *** #### getSupporters ##### getSupporters(`params`) Get users that are supporting a user (they have sent them a tip). Example: ```ts const { data: supporters } = await audiusSdk.users.getSupporters({ id: 'eAZl3', }) console.log(supporters) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing information about the supporters as described below. ```ts { amount: string; rank: number; sender: { albumCount: number; artistPickTrackId?: string; bio?: string; coverPhoto?: { _2000?: string; _640?: string; }; doesFollowCurrentUser?: boolean; ercWallet: string; followeeCount: number; followerCount: number; handle: string; id: string; isAvailable: boolean; isDeactivated: boolean; isVerified: boolean; location?: string; name: string; playlistCount: number; profilePicture?: { _1000x1000?: string; _150x150?: string; _480x480?: string; }; repostCount: number; splWallet: string; supporterCount: number; supportingCount: number; totalAudioBalance: number; trackCount: number; }; }[]; ``` *** #### getSupportedUsers ##### getSupportedUsers(`params`) Get users that a user is supporting (they have sent them a tip). Example: ```ts const { data: supportedUsers } = await audiusSdk.users.getSupportedUsers({ id: 'eAZl3', }) console.log(supportedUsers) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :----------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of users to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of items containing information about the supported users as described below. ```ts { amount: string; rank: number; receiver: { albumCount: number; artistPickTrackId?: string; bio?: string; coverPhoto?: { _2000?: string; _640?: string; }; doesFollowCurrentUser?: boolean; ercWallet: string; followeeCount: number; followerCount: number; handle: string; id: string; isAvailable: boolean; isDeactivated: boolean; isVerified: boolean; location?: string; name: string; playlistCount: number; profilePicture?: { _1000x1000?: string; _150x150?: string; _480x480?: string; }; repostCount: number; splWallet: string; supporterCount: number; supportingCount: number; totalAudioBalance: number; trackCount: number; }; }[]; ``` *** #### getTopTrackTags ##### getTopTrackTags(`params`) Get the most used track tags by a user. Example: ```ts const { data: tags } = await audiusSdk.users.getTopTrackTags({ id: 'eAZl3', }) console.log(tags) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :------------------------------------------------------------ | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of tags to return. Default value is **10** | *Optional* | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns Returns a `Promise` containing an object with a `data` field. `data` is an array of strings representing the tags ```ts string[] ``` *** #### getTracksByUser ##### getTracksByUser(`params`) Get a user's tracks. Example: ```ts const { data: tracks } = await audiusSdk.users.getTracksByUser({ id: 'eAZl3', }) console.log(tracks) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :-------------- | :---------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | :----------- | | `id` | `string` | The ID of the user | **Required** | | `limit` | `number` | The maximum number of tracks to return. Default value is **10** | *Optional* | | `offset` | `number` | The offset to apply to the list of results. Default value is **0** | *Optional* | | `filterTracks` | `GetTracksByUserFilterTracksEnum` (can be imported from `@audius/sdk`) | A filter to apply to the returned tracks. Default value is **GetTracksByUserFilterTracksEnum.All** | *Optional* | | `query` | `string` | A query to search for in a user's tracks | *Optional* | | `sortDirection` | `GetTracksByUserSortDirectionEnum` (can be imported from `@audius/sdk`) | A sort direction to apply to the returned tracks. Default value is **GetTracksByUserSortDirectionEnum.Asc** | *Optional* | | `sortMethod` | `GetTracksByUserSortMethodEnum` (can be imported from `@audius/sdk`) | A sort method to apply to the returned tracks | *Optional* | ##### Returns The return type is the same as [`getBulkTracks`](tracks#getbulktracks) *** #### getUserByHandle ##### getUserByHandle(`params`) Get a user by their handle. Example: ```ts const { data: user } = await audiusSdk.users.getUserByHandle({ handle: 'skrillex', }) console.log(user) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :------- | :------- | :------------------------------------ | :----------- | | `handle` | `string` | The handle of the user | **Required** | | `userId` | `string` | The ID of the user making the request | *Optional* | ##### Returns The return type is the same as [`getUser`](#getuser) *** #### searchUsers ##### searchUsers(`params`) Search for users. Example: ```ts const { data: users } = await audiusSdk.users.searchUsers({ query: 'skrillex', }) console.log(users) ``` ##### Params Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------- | :------------------------------- | :---------------------------------------------------- | :--------- | | `query` | `string` | The query for which to search | *Optional* | | `offset` | `number` | The number of items to skip (for pagination) | *Optional* | | `limit` | `number` | The number of items to fetch (for pagination) | *Optional* | | `genre` | [`Genre[]`](tracks#genre-values) | Array of genres to filter by | *Optional* | | `sortMethod` | `string` | Sort method: `'relevant'`, `'popular'`, or `'recent'` | *Optional* | | `isVerified` | `string` | Filter to only verified users | *Optional* | ##### Returns The return type is the same as [`getFollowers`](#getfollowers) *** #### updateUser ##### updateUser(`params`, `requestInit?`) Update a user profile. To update a profile picture or cover photo, first upload the image via the [Uploads API](/sdk/uploads) and pass the resulting CID in `metadata`. Example: ```ts import fs from 'fs' // Upload a new profile picture const profilePicBuffer = fs.readFileSync('path/to/profile-pic.png') const profilePicUpload = audiusSdk.uploads.createImageUpload({ file: { buffer: Buffer.from(profilePicBuffer), name: 'profilePic.png', }, }) const profilePicCid = await profilePicUpload.start() await audiusSdk.users.updateUser({ id: '7eP5n', userId: '7eP5n', metadata: { bio: 'up and coming artist from the Bronx', profilePicture: profilePicCid, }, }) ``` ##### `params` Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------- | :---------------------- | :------------------------------------ | :----------- | | `id` | `string` | The ID of the user | **Required** | | `userId` | `string` | The ID of the user (same as id) | **Required** | | `metadata` | `UpdateUserRequestBody` | An object with details about the user | **Required** | `UpdateUserRequestBody` — all fields are optional, only include the fields you want to change: | Name | Type | Description | | :-------------------- | :---------------- | :-------------------------------------------- | | `name` | `string` | Display name | | `handle` | `string` | User handle (set only if not already set) | | `bio` | `string` | User bio | | `location` | `string` | User location | | `website` | `string` | Website URL | | `donation` | `string` | Donation link | | `twitterHandle` | `string` | Twitter handle (without @) | | `instagramHandle` | `string` | Instagram handle (without @) | | `tiktokHandle` | `string` | TikTok handle (without @) | | `profilePicture` | `string` | Profile picture CID or URL (from Uploads API) | | `profilePictureSizes` | `string` | Profile picture sizes metadata | | `coverPhoto` | `string` | Cover photo CID or URL (from Uploads API) | | `coverPhotoSizes` | `string` | Cover photo sizes metadata | | `profileType` | `'label' \| null` | Set to `'label'` for record label profiles | | `isDeactivated` | `boolean` | Whether the user is deactivated | | `artistPickTrackId` | `string` | Track hash ID for artist pick | | `allowAiAttribution` | `boolean` | Whether to allow AI attribution | | `splUsdcPayoutWallet` | `string` | Solana USDC payout wallet address | | `coinFlairMint` | `string` | Coin flair mint address | > ##### `requestInit` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with the block hash (`blockHash`) and block number (`blockNumber`) for the transaction. ```ts { blockHash: string blockNumber: number } ``` *** #### followUser ##### followUser(`params`, `requestInit?`) Follow a user. Example: ```ts await audiusSdk.users.followUser({ userId: '7eP5n', followeeUserId: '2kN2a', // User id to follow }) ``` ##### `params` Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------------- | :------- | :--------------------------- | :----------- | | `userId` | `string` | The ID of the user | **Required** | | `followeeUserId` | `string` | The ID of the user to follow | **Required** | > ##### `requestInit` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with the block hash (`blockHash`) and block number (`blockNumber`) for the transaction. ```ts { blockHash: string blockNumber: number } ``` *** #### unfollowUser ##### unfollowUser(`params`, `requestInit?`) Unfollow a user. Example: ```ts await audiusSdk.users.unfollowUser({ userId: '7eP5n', followeeUserId: '2kN2a', // User id to unfollow }) ``` ##### `params` Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :--------------- | :------- | :----------------------------- | :----------- | | `userId` | `string` | The ID of the user | **Required** | | `followeeUserId` | `string` | The ID of the user to unfollow | **Required** | > ##### `requestInit` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with the block hash (`blockHash`) and block number (`blockNumber`) for the transaction. ```ts { blockHash: string blockNumber: number } ``` *** #### subscribeToUser ##### subscribeToUser(`params`, `requestInit?`) Subscribe to a user. Example: ```ts await audiusSdk.users.subscribeToUser({ userId: '7eP5n', subscribeeUserId: '2kN2a', // User id to subscribe to }) ``` ##### `params` Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------------- | :------- | :--------------------------------- | :----------- | | `userId` | `string` | The ID of the user | **Required** | | `subscribeeUserId` | `string` | The ID of the user to subscribe to | **Required** | > ##### `requestInit` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with the block hash (`blockHash`) and block number (`blockNumber`) for the transaction. ```ts { blockHash: string blockNumber: number } ``` *** #### unsubscribeFromUser ##### unsubscribeFromUser(`params`, `requestInit?`) Unsubscribe from a user. Example: ```ts await audiusSdk.users.unsubscribeFromUser({ userId: '7eP5n', subscribeeUserId: '2kN2a', // User id to unsubscribe from }) ``` ##### `params` Create an object with the following fields and pass it as the first argument, as shown in the example above. | Name | Type | Description | Required? | | :----------------- | :------- | :------------------------------------- | :----------- | | `userId` | `string` | The ID of the user | **Required** | | `subscribeeUserId` | `string` | The ID of the user to unsubscribe from | **Required** | > ##### `requestInit` You can pass an optional [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) object as the second argument to customize the underlying fetch request (e.g. set custom headers, abort signal, etc.). ##### Returns Returns a `Promise` containing an object with the block hash (`blockHash`) and block number (`blockNumber`) for the transaction. ```ts { blockHash: string blockNumber: number } ``` :::info[Built with Audius] This Audius Go SDK was built by the community! *** **Useful Links** * Check out the [Code on GitHub](https://github.com/alecsavvy/gaudius) for the latest information. ::: *** ### Usage #### Library ```bash go get github.com/alecsavvy/gaudius ``` ```go package main import "github.com/alecsavvy/gaudius" func main() { sdk, err := gaudius.NewSdk() if err != nil { log.Fatal("sdk init failed: ", err) } } ``` #### Examples ```bash git clone https://github.com/alecsavvy/gaudius.git make example tx-subscriber ``` :::info[Built with Audius] This Audius Music Unreal Engine Plugin was built by the community! *** **Useful Links** * Download from the [Unreal Marketplace](https://www.unrealengine.com/marketplace/en-US/product/audius-music) * Check out the [Code on GitHub](https://github.com/DigiKrafting/Audius_Unreal_Plugin) for the latest information. ::: *** ### Usage #### In Editor Drag the `Audius_Player_Actor` into your level and configure options. {/* prettier-ignore */}
This example uses port 5173 on localhost.
![actor.png](/img/community-apps/unreal-engine-plugin/actor.png) #### C++ Usage Add "Audius" to the `PublicDependencyModuleNames` in your `_project_.Build.cs` ```cpp PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "GameplayTags", "Audius" }); ``` ```cpp #include "Audius_Actor_Base.h" #include "Kismet/GameplayStatics.h" ``` ```cpp FTransform Audius_Actor_SpawnTransform(FRotator::ZeroRotator, FVector::ZeroVector); AAudius_Actor_Base* Audius_Actor_Base = Cast(UGameplayStatics::BeginDeferredActorSpawnFromClass(this, AAudius_Actor_Base::StaticClass(), Audius_Actor_SpawnTransform)); if (Audius_Actor_Base != nullptr) { Audius_Actor_Base->Audius_Actor_Type = EAudius_Actor_Type::Player; Audius_Actor_Base->Audius_Queue_Ended_Action = EAudius_Queue_Ended_Action::Replay; Audius_Actor_Base->Audius_Default_Stream = EAudius_Default_Stream::Trending_Underground; Audius_Actor_Base->Audius_Auto_Play = false; UGameplayStatics::FinishSpawningActor(Audius_Actor_Base, Audius_Actor_SpawnTransform); } ``` ### Overview The Audius Ethereum contracts are meant to accomplish the following goals for the Audius protocol: * Create the Audius token through an ERC-20 * Keep track of different service versions * Allow service providers to stake and register services to run * Allow delegation from users holding Audius token to service providers running services on the network * Allow network to mint new tokens for stakers and delegators to earn staking rewards * Enable protocol governance to carry out protocol actions such as slash, and static value updates :::info All contracts are built on top of [OpenZeppelin's Proxy pattern](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies) through `AudiusAdminUpgradeabilityProxy` which extends `AdminUpgradeabilityProxy`, enabling logic upgrades to be performed through the [Governance contract](#governance). ::: *** ### Contracts > [Github Code available here](https://github.com/AudiusProject/apps/tree/main/eth-contracts/contracts) #### AudiusToken > This contract defines the Audius Protocol token, `$AUDIO` The `$AUDIO` token is a ERC-20 token contract with initial supply of 1 billion tokens, each divisible up to 18 decimal places, and is `Mintable`, `Pausable`, and `Burnable`. | Mainnet Contract | Sepolia Testnet Contract | | ----------------------------------------------------------- | ---------------------------------------------------------------- | | [`0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998`][AudiusToken] | [`0x1376180Ee935AA64A27780F4BE97726Df7B0e2B2`][AudiusToken_test] | [AudiusToken]: https://etherscan.io/address/0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998#code [AudiusToken_test]: https://sepolia.etherscan.io/address/0x1376180Ee935AA64A27780F4BE97726Df7B0e2B2 #### ClaimsManager > This contract is responsible for allocating and minting new tokens as well as managing claim > rounds. A claim round is a period of time during which service providers with valid stakes can retrieve the reward apportioned to them in the network. Claims are processed here and new value transferred to [Staking](#staking) for the claimer, but the values in both [ServiceProviderFactory](#serviceproviderfactory) and [DelegateManager](#delegatemanager) are updated through calls to [DelegateManager](#delegatemanager). | Mainnet Contract | Sepolia Testnet Contract | | ------------------------------------------------------------- | ------------------------------------------------------------------ | | [`0x44617F9dCEd9787C3B06a05B35B4C779a2AA1334`][ClaimsManager] | [`0xcdFFAE230aeDC376478b16f369489A3b450fc2c8`][ClaimsManager_test] | [ClaimsManager]: https://etherscan.io/address/0x44617F9dCEd9787C3B06a05B35B4C779a2AA1334#code [ClaimsManager_test]: https://sepolia.etherscan.io/address/0xcdFFAE230aeDC376478b16f369489A3b450fc2c8 #### DelegateManager > This contract is responsible for tracking delegation state, making claims, and handling slash > operations. This contract allows any Audio Token holder to delegate to an existing Node Operator, earning rewards by providing additional stake the Node Operator while allocating a known percentage of their rewards to the Node Operator. This contract manages the proportional distribution of stake between the Node Operator and delegators. All claim and slash operations flow through this contract in order to update values tracked outside of the [Staking contract](#staking) appropriately and maintain consistency between total value within the [Staking contract](#staking) and value tracked by the [DelegateManager contract](#delegatemanager) and the [ServiceProviderFactory contract](#serviceproviderfactory). | Mainnet Contract | Sepolia Testnet Contract | | --------------------------------------------------------------- | -------------------------------------------------------------------- | | [`0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225`][DelegateManager] | [`0xDA74d6FfbF268Ac441404f5a61f01103451E8697`][DelegateManager_test] | [DelegateManager]: https://etherscan.io/address/0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225#code [DelegateManager_test]: https://sepolia.etherscan.io/address/0xDA74d6FfbF268Ac441404f5a61f01103451E8697 #### EthRewardsManager | Mainnet Contract | Sepolia Testnet Contract | | ----------------------------------------------------------------- | ---------------------------------------------------------------------- | | [`0x5aa6B99A2B461bA8E97207740f0A689C5C39C3b0`][EthRewardsManager] | [`0x563483ccD66a49Ca730275F8cf37Dd3E6Da864f1`][EthRewardsManager_test] | [EthRewardsManager]: https://etherscan.io/address/0x5aa6B99A2B461bA8E97207740f0A689C5C39C3b0#code [EthRewardsManager_test]: https://sepolia.etherscan.io/address/0x563483ccD66a49Ca730275F8cf37Dd3E6Da864f1 #### Governance > This contract allows protocol participants to change protocol direction by submitting and voting > on proposals. Each proposal represents an executable function call on a contract in the [Registry](#registry). Once submitted, there is a period of time during which other participants can submit their votes - `Yes` or `No` - on the proposal. After the voting period has concluded, the proposal outcome is calculated as a the sum of the stakes of the `Yes` voters minus the sum of the stakes of the `No` voters. Any non-negative value results in a successful proposal, at which point the specified function call is executed, and the proposal is closed. Only addresses that have staked in [Staking.sol](#staking) and are represented through the [Registry](#registry) can submit and vote on proposals. | Mainnet Contract | Sepolia Testnet Contract | | ---------------------------------------------------------- | --------------------------------------------------------------- | | [`0x4DEcA517D6817B6510798b7328F2314d3003AbAC`][Governance] | [`0x04973b4416f7e3D62374Ef8b5ABD4a98e4dD401C`][Governance_test] | [Governance]: https://etherscan.io/address/0x4DEcA517D6817B6510798b7328F2314d3003AbAC#code [Governance_test]: https://sepolia.etherscan.io/address/0x04973b4416f7e3D62374Ef8b5ABD4a98e4dD401C #### Registry Contract through which external clients and Governance interact with the remaining contracts within the protocol. Each contract is registered using a key with which its address can be queried. | Mainnet Contract | Sepolia Testnet Contract | | -------------------------------------------------------- | ------------------------------------------------------------- | | [`0xd976d3b4f4e22a238c1A736b6612D22f17b6f64C`][Registry] | [`0xc682C2166E11690B64338e11633Cb8Bb60B0D9c0`][Registry_test] | [Registry]: https://etherscan.io/address/0xd976d3b4f4e22a238c1A736b6612D22f17b6f64C#code [Registry_test]: https://sepolia.etherscan.io/address/0xc682C2166E11690B64338e11633Cb8Bb60B0D9c0 #### ServiceProviderFactory > This contract is responsible for tracking Service Provider state within the Audius network. A service provider is the account associated with a given service endpoint. Each service provider can increase/decrease stake within dynamic bounds determined by the combination of endpoints they have registered, accept delegation from other token holders, define a reward cut for delegation, and continue registering endpoints as necessary. This contract forwards staking requests to the actual [Staking](#staking) contract but tracks the amount of stake for the deployer - [Staking](#staking) tracks the sum of delegate stake + deployer stake. | Contract | Sepolia Testnet Contract Mainnet | | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | | [`0xD17A9bc90c582249e211a4f4b16721e7f65156c8`][ServiceProviderFactory] | [`0x377BE01aD31360d0DFB16035A4515954395A8185`][ServiceProviderFactory_test] | [ServiceProviderFactory]: https://etherscan.io/address/0xD17A9bc90c582249e211a4f4b16721e7f65156c8#code [ServiceProviderFactory_test]: https://sepolia.etherscan.io/address/0x377BE01aD31360d0DFB16035A4515954395A8185
A Note on Terminology Through out the Smart Contracts that define the Audius protocol, the term "service provider" is used along with the "service endpoint(s)" that they operate. More current terminology is "Node Operator" and "Audius Node" respectively. * **Old**: `Service Providers` operate `service endpoints` * **New**: `Node Operators` operate `Audius Nodes`
#### ServiceTypeManager > This contract is responsible for maintaining known `service types`, associated versioning > information and service type stake requirements within the Audius Protocol. Service types are used to identify services being registered within the protocol, for example `creator-node` or `discovery-provider`. Service type stake requirements enforce a minimum and maximum stake amount for each endpoint of a given type that service providers register. | Mainnet Contract | Sepolia Testnet Contract | | ------------------------------------------------------------------ | ----------------------------------------------------------------------- | | [`0x9EfB0f4F38aFbb4b0984D00C126E97E21b8417C5`][ServiceTypeManager] | [`0x9fd76d2cD48022526F3a164541E6552291F4a862`][ServiceTypeManager_test] | [ServiceTypeManager]: https://etherscan.io/address/0x9EfB0f4F38aFbb4b0984D00C126E97E21b8417C5#code [ServiceTypeManager_test]: https://sepolia.etherscan.io/address/0x9fd76d2cD48022526F3a164541E6552291F4a862
A Note on Terminology Through out the Smart Contracts that define the Audius protocol, the terms `creator-node` and `discovery-provider` are used to define `service types`. More current terminology is `content-node` and `discovery-node` are `Audius Node` types respectively. * **Old**: `creator-node` and `discovery-provider` are `service types` * **New**: `content-node` and `discovery-node` are `Audius Node` types.
#### Staking > This contract manages token staking functions and state across the Audius Protocol For every service provider address in Audius Protocol, this contract: * Stores tokens and manages account balances * Tracks total stake history * The total stake (represented as the sun of the deployer stake plus the delegate stake) * Tracks last claim block | Mainnet Contract | Sepolia Testnet Contract | | ------------------------------------------------------- | ------------------------------------------------------------ | | [`0xe6D97B2099F142513be7A2a068bE040656Ae4591`][Staking] | [`0x5bcF21A4D5Bab9B0869B9c55D233f80135C814C6`][Staking_test] | [Staking]: https://etherscan.io/address/0xe6D97B2099F142513be7A2a068bE040656Ae4591#code [Staking_test]: https://sepolia.etherscan.io/address/0x5bcF21A4D5Bab9B0869B9c55D233f80135C814C6 #### TrustedNotifierManager This contract serves as the on chain registry of trusted notifier services. Other services may look up and adjust their selected trusted notifier accordingly. | Contract | Sepolia Testnet Contract Mainnet | | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | | [`0x6f08105c8CEef2BC5653640fcdbBE1e7bb519D39`][TrustedNotifierManager] | [`0x71f8D2aC2f63A481d597d2A6cc160787A048525C`][TrustedNotifierManager_test] | [TrustedNotifierManager]: https://etherscan.io/address/0x6f08105c8CEef2BC5653640fcdbBE1e7bb519D39#code [TrustedNotifierManager_test]: https://sepolia.etherscan.io/address/0x71f8D2aC2f63A481d597d2A6cc160787A048525C #### WormholeClient This contract serves as the interface between the Audius Protocol and [Wormhole](https://solana.com/ecosystem/wormhole). | Mainnet Contract | Sepolia Testnet Contract | | -------------------------------------------------------------- | ------------------------------------------------------------------- | | [`0x6E7a1F7339bbB62b23D44797b63e4258d283E095`][wormholeclient] | [`0x2Eb3BF862e7a724A151e78CEB1173FB332E174a0`][wormholeclient_test] | [wormholeclient]: https://etherscan.io/address/0x6E7a1F7339bbB62b23D44797b63e4258d283E095#code [wormholeclient_test]: https://sepolia.etherscan.io/address/0x2Eb3BF862e7a724A151e78CEB1173FB332E174a0 Links and resources from [audius.co/agents.md](https://audius.co/agents.md). ### Docs & API * [Docs](https://docs.audius.co) * [API](https://api.audius.co) * [API Reference](/api) * [API Plans (keys)](https://api.audius.co/plans) * [SDK (npm)](https://www.npmjs.com/package/@audius/sdk) * [SDK Overview](/sdk) * [SDK Tracks](/sdk/tracks) * [SDK Users](/sdk/users) * [SDK Playlists](/sdk/playlists) ### Guides * [Create Audius App](/developers/guides/create-audius-app) * [Log in with Audius](/developers/guides/log-in-with-audius) * [Image Loading & Mirrors](/developers/guides/image-mirrors) ### Protocol & Tools * [Open Audio Protocol](https://openaudio.org) * [Protocol Dashboard](https://dashboard.audius.org) * [Link Profile](/reference/protocol-dashboard/link-profile) - Connect an Audius account to the Protocol Dashboard ### Developer Resources * [GitHub Org](https://github.com/audiusproject) * [Audius (app)](https://audius.co) * [API Settings](https://audius.co/settings) - Manage Your Apps, API keys * [skill.md](https://audius.co/skill.md) - SDK setup, API credentials, code snippets * [llms.txt](https://audius.co/llms.txt) - AI overview ### Open Audio Protocol * [OAP agents.md](https://openaudio.org/agents.md) * [OAP skill.md](https://openaudio.org/skill.md) * [OAP llms.txt](https://openaudio.org/llms.txt) ### Programs :::info[Testnet on Mainnet?] Please note that all Audius Protocol Testnet Programs are deployed to Solana Mainnet. ::: > [Github Code available here](https://github.com/AudiusProject/apps/tree/main/solana-programs) #### Audius Plays This program handles recording track play counts on chain. Every time a user listens to a track on Audius, it is recorded on the Solana blockchain. | Mainnet Program | Testnet Program | | ------------------------------------------------------------- | ------------------------------------------------------------------ | | [`7K3UpbZViPnQDLn2DAM853B9J5GBxd1L1rLHy4KqSmWG`][AudiusPlays] | [`ApR7QbouRviwoE6nLL83omE6GdtM6yUJAsuXw5sPDCQV`][AudiusPlays_test] | [AudiusPlays]: https://solscan.io/account/7K3UpbZViPnQDLn2DAM853B9J5GBxd1L1rLHy4KqSmWG [AudiusPlays_test]: https://solscan.io/account/ApR7QbouRviwoE6nLL83omE6GdtM6yUJAsuXw5sPDCQV * [Code on GitHub](https://github.com/AudiusProject/apps/tree/main/solana-programs/track_listen_count) #### Claimable Tokens This program powers the Audis reward system, and allows Solana non-custodial token accounts to be created for Audius users that are represented by an Ethereum wallet. This program is also referred to as the "User Bank". | Mainnet Program | Testnet Program | | ----------------------------------------------------------------- | ---------------------------------------------------------------------- | | [`Ewkv3JahEFRKkcJmpoKB7pXbnUHwjAyXiwEo4ZY2rezQ`][ClaimableTokens] | [`2sjQNmUfkV6yKKi4dPR8gWRgtyma5aiymE3aXL2RAZww`][ClaimableTokens_test] | [ClaimableTokens]: https://solscan.io/account/Ewkv3JahEFRKkcJmpoKB7pXbnUHwjAyXiwEo4ZY2rezQ [ClaimableTokens_test]: https://solscan.io/account/2sjQNmUfkV6yKKi4dPR8gWRgtyma5aiymE3aXL2RAZww * [Code on GitHub](https://github.com/AudiusProject/apps/tree/main/solana-programs/claimable-tokens) #### Payment Router This program is responsible for distributing any SPL Token across different provided Solana associated token accounts, with amounts determined by a given percent split. It is intended to be used with `SPL-AUDIO` and `SPL-USDC`. While payments can be made independently of the `Payment Router` program, it is designed to improve space-efficiency and usability off-chain. | Mainnet Program | Testnet Program | | -------------------------------------------------------------- | ------------------------------------------------------------------- | | [`paytYpX3LPN98TAeen6bFFeraGSuWnomZmCXjAsoqPa`][PaymentRouter] | [`sp28KA2bTnTA4oSZ3r9tTSKfmiXZtZQHnYYQqWfUyVa`][PaymentRouter_test] | [PaymentRouter]: https://solscan.io/account/paytYpX3LPN98TAeen6bFFeraGSuWnomZmCXjAsoqPa [PaymentRouter_test]: https://solscan.io/account/sp28KA2bTnTA4oSZ3r9tTSKfmiXZtZQHnYYQqWfUyVa * [Code on GitHub](https://github.com/AudiusProject/apps/tree/main/solana-programs/payment-router) #### Reward Manager This program allows for Audius users to claim rewards given attestations from Node Operators that they have successfully completed a challenge. For example, to claim the “I’ve completed my profile reward,” a user may ask for a set of Discovery Node Operators to provide a cryptographic proof that they have completed the challenge and submit that to chain. If the signatures are valid, tokens are dispensed | Mainnet Program | Testnet Program | | --------------------------------------------------------------- | -------------------------------------------------------------------- | | [`DDZDcYdQFEMwcu2Mwo75yGFjJ1mUQyyXLWzhZLEVFcei`][RewardManager] | [`CDpzvz7DfgbF95jSSCHLX3ERkugyfgn9Fw8ypNZ1hfXp`][RewardManager_test] | [RewardManager]: https://solscan.io/account/DDZDcYdQFEMwcu2Mwo75yGFjJ1mUQyyXLWzhZLEVFcei [RewardManager_test]: https://solscan.io/account/CDpzvz7DfgbF95jSSCHLX3ERkugyfgn9Fw8ypNZ1hfXp * [Code on GitHub](https://github.com/AudiusProject/apps/tree/main/solana-programs/reward-manager) #### Staking Bridge This program has 2 main functions: 1. Swap `SPL-USDC` tokens to `SPL-AUDIO` tokens via the [Raydium AMM Program](https://raydium.io/). 2. Convert `SPL-AUDIO` tokens to `ERC20-AUDIO` tokens via the Wormhole Token Bridge. The methods in this program are intentionally permissionless, allowing any user willing to pay transaction fees to interact. The methods of the Staking Bridge are independent to reduce price impact of swaps and fees associated with bridging tokens. | Mainnet Program | Testnet Program | | -------------------------------------------------------------- | ------------------------------------------------------------------- | | [`stkB5DZziVJT1C1VmzvDdRtdWxfs5nwcHViiaNBDK31`][StakingBridge] | [`stkuyR7dTzxV1YnoDo5tfuBmkuKn7zDatimYRDTmQvj`][StakingBridge_test] | [StakingBridge]: https://solscan.io/account/stkB5DZziVJT1C1VmzvDdRtdWxfs5nwcHViiaNBDK31 [StakingBridge_test]: https://solscan.io/account/stkuyR7dTzxV1YnoDo5tfuBmkuKn7zDatimYRDTmQvj * [Code on GitHub](https://github.com/AudiusProject/apps/tree/main/solana-programs/staking-bridge) The Audius Whitepaper lays the theoretical & technical groundwork for how the platform is built and continues to grow. * [PDF download](https://whitepaper.audius.co) * [Blog post](https://blog.audius.co/posts/the-audius-white-paper-a-decentralized-community-owned-music-sharing-protocol) *** Questions? Ask & engage with the community on: * [Discord](https://discord.com/invite/audius) * [Reddit](https://www.reddit.com/r/audius/) > Help other users identify you by connecting your [Audius][audius-co] account to the [Audius > Protocol Dashboard][protocol-dashboard]. Once you've linked your Audius account, your Profile Picture and Display Name will be visible to users throughout the protocol dashboard. ### Connect to Protocol Dashboard 1. Navigate to the [Audius Protocol Dashboard][protocol-dashboard] 2. Click the "Connect Wallet" button on the upper right {/* prettier-ignore */}
Wallet Connect Button
3. Select your web3 wallet in the wallet selection modal and sign in. {/* prettier-ignore */}
Wallet Selection Modal
4. By default, a Gravatar style icon will be used to represent the wallet across the Dashboard. {/* prettier-ignore */}
Protocol Dashboard default profile icon
*** ### Connect to Audius Profile To connect your Audius profile to the Dashboard, 1. Click the "Connect Audius Profile" button in the upper right corner. {/* prettier-ignore */}
Protocol Dashboard "Connect Audius Profile" button
2. In the modal, confirm your understanding and proceed by clicking the "Connect Profile" button. {/* prettier-ignore */}
Review and confirm the next step by clicking "Connect Profile" in the modal.
3. In the pop over, enter the credentials of the Audius account you want to connect to your Protocol Dashboard account and click "Sign In & Authorize App"
Sign in with your Audius account to continue
:::tip[Already signed in?] If you are already signed in to an Audius account it will be chosen as the default. If you would like to use a different Audius account, be sure to sign out and sign in with the correct account before clicking "Authorize App". If you are not currently signed in to an Audius account, you will be prompted to do so. ::: 4. Your wallet app will present a signature request to confirm the account connection. Sign this message to complete the link. {/* prettier-ignore */}
Using MetaMask as an example, sign the request.
5. Complete! Now your Audius account profile image will be shown on the Audius Protocol Dashboard! {/* prettier-ignore */}
Account icon when an Audius account is connected to the Protocol Dashboard.
{/* prettier-ignore */} [audius-co]: https://audius.co/ [protocol-dashboard]: https://dashboard.audius.org/ ### How Audius Governance Works Governance is the process by which AUDIO token holders enact change to Audius through on-chain proposals. It allows the community to directly shape future iterations of the platform and is the core principle driving Audius’s decentralized infrastructure. In this post, we’ll cover how governance works in Audius, and what you can do as an AUDIO holder to get involved. *** ### Governance Portal :::tip The Audius Protocol Dashboard [Governance tab](https://dashboard.audius.org/#/governance) provides a view of Audius Governance and an interface to vote on proposals with a connected wallet: ::: Here you can see a list of all `Active` and `Resolved` proposals in chronological order along with whether they have passed or failed. Every governance proposal comes with a breakdown of the following parameters: | Parameter | Description | | :-----------: | --------------------------------------------------------- | | `Proposer` | The address responsible for submitting the proposal | | `Description` | A quick synthesis of what the governance proposal entails | | `For` | The amount of votes in favor of the proposal | | `Against` | The amount of votes against the proposal | > All proposals are subject to 5% of staked $AUDIO quorum and 50% majority. This means that for a proposal to pass, at least 5% of all staked $AUDIO must vote on the proposal and more than 50% of the votes must be ‘For’ the proposal. Today, only those running a node may make a proposal on-chain. In future, the set of permitted proposers could be expanded in any way the community sees fit. *** ### Governance Process Effective governance is much more than voting on proposals on-chain, and something that we want to make even more accessible at Audius. Here’s a breakdown of Audius’ evolving governance ecosystem, including the tools, processes and logistics behind AUDIO voting. ```mermaid flowchart LR A(Community Feedback)-->B(Forum Post)-->C(Submit to Governance Portal)-->D(On-Chain Vote)-->E(Execute); ``` Please note that some users may be more inclined than others to facilitate the duration of this process, and we recommend anyone interested in shaping Audius to contribute in whatever ways possible, even if that means just starting a conversation around a topic on Discord! #### On-chain Voting Using Figment’s [most recent governance proposal](https://dashboard.audius.org/#/governance/proposal/9) as an example, you can see that different node operators and delegators voted in favor of extending the voting time from 48 to 72 hours. Given that the total number of votes (1 AUDIO, 1 vote) was above the quorum requirement of \~11M $AUDIO and the 50% majority (100% voted in favor) the proposal passed! In doing so, the changes [from this proposal](https://etherscan.io/tx/0xd4e14895b2a22b48469a43923ab7b30bee75f9a688941933430b3dae9510b8a6) were [executed through the governance contract](https://etherscan.io/tx/0x4396652fb9c1116cec5900f412608dfba7a3ec1b9967f4109a8ec3e09d3a75af), changing the voting window from 48 hours to 72 hours! #### Community MultiSig Once a vote has been passed, the governance contract executes the proposal. However, Audius also features a community multisig as a veto of last resort, referenced in the whitepaper in the “short-circuiting” subsection of the governance section. This means that a set of 9 Audius community members have the ability to stop a malicious proposal from passing. In the event the multisig is used, 6 of the 9 signers must sign a transaction to nullify the proposal. As Audius continues to mature, the community can at any time vote to remove this veto ability from the system as well. More details on the signers of this multisig as well as the intent for its use will be shared in a future blog post. *** ### Evolving Governance Audius governance is an evolving process geared at giving all $AUDIO holders a voice of future iterations of the platform. The process detailed above is likely to change in line with new tools, product upgrades and onramps to allow for all token users to easily review and participate in governance decisions, regardless of their technical knowledge. We’re excited to share more details around governance in the near future and look forward to building out the community-owned streaming protocol that is Audius! ### Contributing Audius welcomes contributions of all sizes from the open source community. Check out the open issues in one of the repos below or reach out on [Discord](https://discord.gg/audius) to get started. *** ### Repositories | Repository | Description | | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | [Protocol](https://github.com/AudiusProject/apps/issues) | Specification of the protocol | | [Audius Docker Compose](https://github.com/AudiusProject/audius-docker-compose/issues) | Launch and manage Audius services using Docker Compose | | [Hedgehog](https://github.com/AudiusProject/hedgehog/issues) | Metamask alternative that manages a user's private key and wallet on the browser | | [React Starter](https://github.com/AudiusProject/audius-sdk-react-starter/issues) | React starter app using the Audius Javascript SDK | *** ### Documentation This site serves as the central hub for documentation on the protocol. If you have feedback, please open an issue or create a pull request on [GitHub](https://github.com/AudiusProject/apps) or use the "Edit this Page" link at the bottom of each page. *** ### Community [Developer Channel on Discord](https://discord.com/channels/557662127305785361/864882574127398913) - Public chat for developer support. Audius is a decentralized, community-owned and artist-controlled music-sharing protocol. Audius provides a blockchain-based alternative to existing streaming platforms to help artists publish and monetize their work and distribute it directly to fans. The mission of the project is to give everyone the freedom to share, monetize, and listen to any audio. The Audius Protocol [repository](https://github.com/AudiusProject/apps) is a mono-repository that has all the pieces that make and support the protocol including smart contracts, services, and other supporting libraries. If you are interested in operating a service, see the [`running a node`](https://docs.openaudio.org) section. If you're interested in contributing to the Audius protocol, explore the code below! ```mermaid flowchart LR A(((Artists)))--->|Publish Content|B([Audius Content Ledger]) B--->|Index Content Metadata|D[(Discovery Node)]; D--->|Discover Content|F; A(((Artists)))--->|Upload Content|C[(Content Node)]; C--->|Stream Content|F(((Fans))); click C href "/learn/architecture/content-node" click D href "/learn/architecture/discovery-node" ``` Audius consists of three demographics of users: Artists (content creators), Fans (content consumers), and Node Operators. Some users check fall into all three demographics! * **Artists** upload tracks, create albums, and share content to their following * **Fans** stream tracks, create playlists, subscribe to & follow artists, and re-share content to their following * **Node Operators** serve app traffic, stream songs, and help secure the network Node Operators can provide one or more of the following services by staking $AUDIO tokens and registering their service: * Discovery node (host an endpoint with SSL support and register endpoint with stake) * Content node (host an endpoint with SSL support and register endpoint with stake) In the above diagram, creators can either run a content node themselves or use one of the network-registered content nodes. For more details on the Audius architecture, see the [Audius protocol whitepaper](/reference/whitepaper). *** ### Audius Services | Service | Description | GitHub | | :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | | [Content Node](/learn/architecture/content-node) | Maintains the availability of users' content on IPFS including user metadata, images, and audio content | [Link](https://github.com/AudiusProject/audius-docker-compose/tree/main/creator-node) | | [Discovery Node](/learn/architecture/discovery-node) | Indexes and stores the contents of the Audius contracts on the Ethereum blockchain for clients to query via an API | [Link](https://github.com/AudiusProject/audius-docker-compose/tree/main/discovery-provider) | | Identity Service | Stores encrypted auth ciphertexts, does Twitter OAuth and relays transactions (pays gas) on behalf of users | [Link](https://github.com/AudiusProject/audius-docker-compose/tree/main/identity-service) | *** ### Audius Smart Contracts & Libs | Lib | Description | | :------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | [`libs`](https://github.com/AudiusProject/apps/tree/main/packages/libs) | An easy interface to the distributed web and Audius services: Identity Service, Discovery Node (discovery provider), Content Node (creator node) | | [`contracts`](https://github.com/AudiusProject/apps/tree/main/contracts) | The smart contracts being developed for the Audius streaming protocol | | [`eth-contracts`](https://github.com/AudiusProject/apps/tree/main/eth-contracts) | The Ethereum smart contracts being developed for the Audius streaming protocol | *** ### Node Operators Quickstart A quick start guide to running Nodes on Audius can be found [here](https://docs.openaudio.org) ### What is Staking? Built as a decentralized protocol on Ethereum, all the content, information and data on Audius is stored and indexed by a growing network of third-party Node Operators. To ensure this content can be trusted and maintained, Node Operators are required to provide collateral or "stake" as a bond to service the protocol. This stake, denominated in $AUDIO, ensures that Node Operators have tokens at risk that can be slashed, or taken, in the event of malicious or poor behavior. :::info[More Information] Ready to learn more, check out the [Staking section](https://docs.openaudio.org) of the docs. ::: *** ### What is Delegating? For users that either do not hold enough $AUDIO to self stake a Node, do not want to operate a Node, or are just looking to get started, Delegation is a great place to get involved. Delegating tokens earns rewards, and increases your ownership of the protocol while supporting Node Operators, assisting in keeping them up and running, which in turns keeps the Audius protocol healthy. :::info[More Information] Ready to learn more, check out the [Delegating section](https://docs.openaudio.org) of the docs. ::: ### How it Works Offering a native token to align all actors creates a parallel incentive unique to web3, and one which allows our early adopters to share in the upside of Audius as we continue to grow. The Audius platform token $AUDIO has three prongs of functionality within the Audius protocol ecosystem: ```mermaid flowchart LR token((Audius Token Utility)); token--->P1[Security] token--->P2[Feature Access] token--->P3[Governance] ``` $AUDIO is staked as collateral for a value-added service such as [operating a Node](https://docs.openaudio.org) or participating in governance. In exchange, Stakers earn ongoing issuance, governance weight, and access to exclusive features. In the future, $AUDIO will govern a global fee pool from value transfers in the network. *** ### Security $AUDIO is staked by Node Operators to secure the network. The larger the stake, the higher the probability of their node being used by fans and artists. Audius is entirely hosted and operated by the community, creating a permissionless ecosystem of Node Operators securing content for the world’s unstoppable streaming protocol. *** ### Feature Access $AUDIO serves as collateral to unlock additional artist tooling. Early examples incubated by the community include artists tokens, badges, and earnings multipliers. In the future, Fans may delegate $AUDIO to specific Artists and curators to share in their growth on the platform. *** ### Governance Staking $AUDIO gives users governance weight to influence the future of the protocol, with each token staked equaling one vote. Every aspect of Audius is governable, aiming to involve even passive fans to voice their opinion over product updates and feature upgrades. Ongoing issuance aligns power with the most active platform users, ensuring $AUDIO tokens are always being funneled to value-added actors. This distribution method, using on-chain metrics, directs issuance to active participants, rather than just the largest stakers. An Audius Content Node is a service that stores and maintains the availability of all content across the Audius network. Content types include user, track, and playlist metadata, images and artwork, and audio content. The Content Node source code is hosted on [GitHub](https://github.com/AudiusProject/audius-docker-compose/tree/main/creator-node) and see the [registered Content Nodes](https://dashboard.audius.org/#/services/content-node) on the Audius Protocol Dashboard. *** ### Design Goals 1. Surface the Audius Storage Protocol for storing and serving images and audio 2. Keep data consistently replicated and available 3. Provide an interface to handle content upload, transcoding, and identification 4. Allow users to maintain agency over where and how their data is stored amongst Content Nodes :::note[Legacy Terminology] The "Content Node" may be referred to as the "Creator Node". These services are the same. ::: *** ### Web Server The Content Node core service is a web server with an HTTP API to process incoming requests and perform the following functions: * user & track metadata upload * user & track image upload * user track file upload * user & track data, metadata, and track file retrieval The web server is a [NodeJS](https://nodejs.org) [Express app](https://expressjs.com/). *** ### Persistent Storage It stores all data in a PostgreSQL database and all images and metadata objects on its file system. Pointers to all content and metadata stored on disk are persisted in the Postgres DB. Postgres is managed in the codebase using the [Sequelize ORM](https://sequelize.org/main/) which includes migrations, models and validations *** ### Redis A [Redis client](https://redis.io/) is used for resource locking, request rate limiting, and limited caching and key storage. Redis is managed in the codebase through the [ioredis npm package](https://github.com/luin/ioredis) *** ### Track Segmenting As defined by the [Audius Whitepaper](/reference/whitepaper), the content node uses [FFMPEG](https://ffmpeg.org/ffmpeg.html) to segment & transcode all uploaded track files before storing/serving. *** ### Data Redundancy As defined by the [Audius Whitepaper](/reference/whitepaper), all content is stored redundantly across multiple Nodes to maximize availability. This is all done automatically - every Node monitors every other Node in the network to ensure minimum redundancy of all data, transferring files as required. An Audius Discovery Node is a service that indexes the metadata and availability of data across the protocol for Audius users to query. The indexed content includes user, track, and album/playlist information along with social features. The data is stored for quick access, updated on a regular interval, and made available for clients via a [RESTful API](/api). The Discovery Node source code is hosted on [GitHub](https://github.com/AudiusProject/audius-docker-compose/tree/main/discovery-provider) and see the [registered Discovery Nodes](https://dashboard.audius.org/#/services/discovery-node) on the Audius Protocol Dashboard. *** ### Design Goals 1. Expose queryable endpoints which listeners/creators can interact with 2. Reliably store relevant blockchain events 3. Continuously monitor the blockchain and ensure stored data is up to date with the network :::note[Legacy Terminology] The "Discovery Node" may be referred to as the "Discovery Provider". These services are the same. ::: *** ### Database {/* TODO: many of these GitHub links are broken */} The Discovery Node uses PostgreSQL. Our Postgres database is managed through [SQLAlchemy](https://www.sqlalchemy.org/), an object relational mapper and [Alembic](http://alembic.zzzcomputing.com/en/latest/index.html), a lightweight database migration tool. The data models are defined in [src/models](https://github.com/AudiusProject/apps/blob/main/packages/discovery-provider/src/models) which is used by alembic to automatically generate the migrations under [alembic/versions](https://github.com/AudiusProject/apps/tree/main/packages/discovery-provider/alembic/versions). You can find the connection defined between alembic and the data models in [alembic/env.py](https://github.com/AudiusProject/apps/tree/main/packages/discovery-provider/alembic/env.py) *** ### Flask The Discovery Node web server serves as the entry point for reading data through the Audius Protocol. All queries are returned as JSON objects parsed from SQLAlchemy query results, and can be found in [src/queries](https://github.com/AudiusProject/apps/tree/main/packages/discovery-provider/src/queries). Some examples of queries include user-specific feeds, track data, playlist data, etc. *** ### Celery Celery is simply a task queue - it allows us to define a set of single tasks repeated throughout the lifetime of the Discovery Node. Currently, a single task `(src/tasks/index.py:update_task()`) handles all database write operations. The Flask application reads from the database and is unaware of data correctness. Celery [worker](https://docs.celeryq.dev/en/stable/reference/celery.worker.html) and [beat](https://docs.celeryq.dev/en/stable/reference/celery.beat.html) are the key underlying concepts behind Celery usage in the Discovery Node. #### Celery Worker Celery worker is the component that actually runs tasks. The primary driver of data availability on Audius is the 'index\_blocks' Celery task. What happens when 'index\_blocks' is actually executed? The Celery task does the following operations: 1. Check whether the latest block is different than the last processed block in the ‘blocks’ table. If so, an array of blocks is generated from the last blockhash present in the database up to the latest block number specified by the block indexing window. Block indexing window is equivalent to the maximum number of blocks to be processed in a single indexing operation 2. Traverse over each block in the block array produced after the above step. In each block, check if any transactions relevant to the Audius Smart Contracts are present. If present, we retrieve specific event information from the associated transaction hash, examples include `creator` and `track` metadata. To do so, the Discovery Node *must* be aware of both the contract ABIs as well as each contract's address - these are shipped with each Discovery Node image. 3. Given operations from Audius contracts in a given block, the task updates the corresponding table in the database. {/* TODO: Audius Storage Protocol link? */} Certain index operations require a metadata fetch from decentralized storage (Audius Storage Protocol). Metadata formats can be found [here](https://github.com/AudiusProject/apps/blob/main/packages/discovery-provider/src/tasks/metadata.py). :::info[Why index blocks instead of using event filters?] This is a great question - the main reason chosen to index blocks in this manner is to handle cases of false progress and rollback. Each indexing task opens a fresh database session, which means database transactions can be reverted at a block level - while rollback handling for the Discovery Node has yet to be implemented, block-level indexing will be immediately useful when it becomes necessary. ::: #### Celery Beat Celery beat is responsible for periodically scheduling index tasks and is run as a separate container from the worker. Details about periodic task scheduling can be found in the [official documentation](http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html). This is an identical container as the [Celery worker](#celery-worker) but is run as a 'beat scheduler' to ensure indexing is run at a periodic interval. By default this interval is `5 seconds`. *** ### Redis A [Redis client](https://redis.io/) is used for several things in the Discovery Node. 1. Caching (internally and externally) 2. As a [broker for Celery](https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html) 3. As a mechanism for locking to ensure single execution contexts for Celery jobs ### Elastic Search Elastic Search is used to denormalize data and supports certain queries (Feed, Search, Related Artists, etc.). Elastic Search data is populated and kept up to date by database triggers that live on the Postgres database. ETL code for the Elastic Search layer is found in the [es-indexer](https://github.com/AudiusProject/apps/tree/main/packages/discovery-provider/es-indexer). {/* TODO: update es-indexer link */} :::info[ERN Versioning] The following is provided based on [ERN3.8](/distributors/specification/deal-types/recommended) Please note that exact DDEX fields will depend on the specific ERN version. ::: *** ### General Metadata Mapping #### Required Fields The following metadata fields are required for content to be listed on Audius and are examined and pulled from a DDEX delivery in cascading precedence. ##### imageFile :::note The `imageFile` maps to two DDEX Fields ::: * `/ResourceList/Image/ImageDetailsByTerritory[TerritoryCode="Worldwide"]/TechnicalImageDetails/File/FilePath` * `/ResourceList/Image/ImageDetailsByTerritory[TerritoryCode="Worldwide"]/TechnicalImageDetails/File/FileName` ##### releaseDate :::note The resource will not be published on the Audius platform until the following condition is met: `current date ≥ max(releaseDate, validity start date from the corresponding deal)` However the date displayed in the Audius interface will be this `releaseDate` value (determined by the hierarchy below). ::: 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/ReleaseDate` 2. `/ReleaseList/Release/GlobalOriginalReleaseDate` 3. `/DealList/ReleaseDeal/Deal/ValidityPeriod/StartDate` ##### userId :::note Not technically an Audius SDK metadata field, but uploaded as part of the track/album ::: 1. checks each artist name (in order) against Audius database of OAuthed display names, and uses the first match *** #### Optional Fields The following fields are optional for content to be listed on Audius and are examined and pulled from a DDEX delivery in cascading precedence. ##### ddexReleaseIds 1. `/ReleaseList/Release/ReleaseId` :::note The following fields are parsed and preserved from this field: `PartyId`, `CatalogNumber`, `ICPN`, `GRid`, `ISAN`, `ISBN`, `ISMN`, `ISRC`, `ISSN`, `ISTC`, `ISWC`, `MWLI`, `SICI`, and `ProprietaryId` ::: ##### description The DDEX standard includes a `MarketingComments` field that is rarely used, but is available. *** #### Unused Fields The following Audius SDK Fields are not used by DDEX and have no mapping. ##### child elements of `/ReleaseList/Release/` The following child elements are parsed and stored in the separate DDEX server. Audius does not store these child fields and they are not used. * `ReferenceTitle/TitleText` * `ReferenceTitle/SubTitle` ##### child elements of `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/` The following child elements are parsed and stored in the separate DDEX server. Audius does not store these child fields and they are not used. * `Title[@TitleType='DisplayTitle']/TitleText` (used in albums/EPs but not single tracks) * `Title[@TitleType='DisplayTitle']/SubTitle` * `Title[@TitleType='FormalTitle']/TitleText` * `Title[@TitleType='FormalTitle']/SubTitle` ##### child elements of `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/ResourceGroup/` The following child elements are parsed and stored in the separate DDEX server. Audius does not store these child fields and they are not used. * `SequenceNumber` * `ResourceGroupContentItem/ResourceType` * `ResourceGroupContentItem/ReleaseResourceReference` * `ResourceGroupContentItem/IsInstantGratificationResource` *** ### Track Metadata Mapping #### Required Fields The following fields are required for content to be listed on Audius and are examined and pulled from a DDEX delivery in cascading precedence. ##### genre 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/Genre/SubGenre` 2. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/Genre/GenreText` 3. `/ResourceList/SoundRecording/SoundRecordingDetailsByTerritory[TerritoryCode="Worldwide"]/Genre/SubGenre` 4. `/ResourceList/SoundRecording/SoundRecordingDetailsByTerritory[TerritoryCode="Worldwide"]/Genre/GenreText` ##### title 1. `/ResourceList/SoundRecording/ReferenceTitle/TitleText` :::note Subtitle is currently ignored/unused in the both the SoundRecording and release and are stored in the separate DDEX server but not in the Audius network. ::: ##### audioFile This is the actual audio file, not technically an Audius SDK metadata field, but uploaded in the same SDK function. Note that this DDEX field is a relative path to the file within the delivery :::note The `audioFile` maps to two DDEX Fields ::: * `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/TechnicalSoundRecordingDetails/File/FilePath` * `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/TechnicalSoundRecordingDetails/File/FileName` *** #### Optional Fields The following fields are optional for content to be listed on Audius and are examined and pulled from a DDEX delivery in cascading precedence. ##### artists The `/PartyName/FullName` child element is used as the artist’s name and the `SequenceNumber` attribute to preserve order 1. `/ResourceList/SoundRecording/SoundRecordingDetailsByTerritory[TerritoryCode="Worldwide"]/DisplayArtist` 2. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/DisplayArtist` ##### copyrightLine This is only used if both `year` *AND* `text` are non-empty. The child elements that are parsed are: `Year` and `CLineText` 1. `/ResourceList/SoundRecording/SoundRecordingDetailsByTerritory[TerritoryCode="Worldwide"]/CLine` 2. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/CLine` 3. `/ReleaseList/Release/CLine` ##### indirectResourceContributors :::note The following children elements are parsed and stored in Audius: `PartyName`/`FullName`, `SequenceNumber`, and `IndirectResourceContributorRole` ::: 1. `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/IndirectResourceContributor` ##### isrc 1. `/ResourceList/SoundRecording/SoundRecordingId/ISRC` ##### iswc 1. `/ReleaseList/Release/ReleaseId/ISWC` ##### parentalWarningType 1. `/ResourceList/SoundRecording/SoundRecordingDetailsByTerritory[TerritoryCode="Worldwide"]/ParentalWarningType` 2. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/ParentalWarningType` ##### previewStartSeconds Preview length is 30 seconds starting at the `previewStartSeconds` into the track’s audio, even if a longer or shorter duration is given. does not support using an external file for the preview 1. `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/TechnicalSoundRecordingDetails/PreviewDetails/StartPoint` 2. only when `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/TechnicalSoundRecordingDetails/IsPreview` is true ##### producerCopyrightLine 1. `/ResourceList/SoundRecording/SoundRecordingDetailsByTerritory[TerritoryCode="Worldwide"]/PLine` 2. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/PLine` 3. `/ReleaseList/Release/PLine` :::note This is only used if both `year` *AND* `text` are non-empty. The child elements that are parsed are: `Year` and `CLineText` ::: ##### resourceContributors 1. `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/ResourceContributor` :::note The following children elements are parsed and stored in Audius: `PartyName`/`FullName`, `SequenceNumber`, and `ResourceContributorRole` ::: ##### rightsController 1. `/ResourceList/SoundRecording/SoundDetailsByTerritory[TerritoryCode="Worldwide"]/RightsController` :::note The following children elements are parsed and stored in Audius: `PartyName`/`FullName`, `RightsShareUnknown`, and `RightsControllerRole` ::: *** ### Album Metadata Mapping #### Required Fields The following fields are required for content to be listed on Audius and are examined and pulled from a DDEX delivery in cascading precedence. ##### albumName 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/Title[@TitleType='DisplayTitle']/TitleText` :::note Subtitle is currently ignored/unused in the both the SoundRecording and release and are stored in the separate DDEX server but not in the Audius network. ::: ##### genre 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/Genre/SubGenre` 2. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/Genre/GenreText` ##### audioFiles This is an array of audio files which the album is comprised of, not technically an Audius SDK metadata field, but uploaded as part of the album. 1. each `/ReleaseList/Release/` except the release with the attribute `IsMainRelease="true"` ##### trackMetadatas Metadata about each track in the album, not technically an Audius SDK metadata field, but uploaded as part of the album 1. each `/ReleaseList/Release/` except the release with the attribute `IsMainRelease="true"` *** #### Optional Fields The following fields are optional for content to be listed on Audius and are examined and pulled from a DDEX delivery in cascading precedence. ##### artists The `/PartyName/FullName` child element is used as the artist’s name and the `SequenceNumber` attribute to preserve order 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/DisplayArtist` ##### copyrightLine 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/CLine` 2. `/ReleaseList/Release/CLine` :::note This is only used if both `year` *AND* `text` are non-empty. The child elements that are parsed are: `Year` and `CLineText` ::: ##### parentalWarningType 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/ParentalWarningType` ##### producerCopyrightLine 1. `/ReleaseList/Release/ReleaseDetailsByTerritory[TerritoryCode="Worldwide"]/PLine` 2. `/ReleaseList/Release/PLine` :::note This is only used if both `year` *AND* `text` are non-empty. The child elements that are parsed are: `Year` and `PLineText` ::: ##### upc 1. `/ReleaseList/Release/ReleaseId/ICPN` :::note ICPN (or "International Code Product Number") has an `IsEAN` attribute which determines if it’s an EAN (or "European Article Number") or UPC ("Universal Product Code" — only used in US and Canada). Audius uses these interchangeably and just set it as UPC even if it’s an EAN. ::: ### General Guidance :::info[Delivery processing] [TikiLabs](https://tikilabs.com) is a facilitator of distributing DDEX directly to the Audius Protocol. For inquiries or support, reach out at [support@audius.co](mailto\:support@audius.co?subject=DDEX%20Support). ::: * ERN3 is the preferred DDEX specification for bulk ingestion into Audius. See the [ERN3 details](/distributors/specification/deal-types/recommended) for more details around submission choreography. * Audius only accepts price information using absolute prices (e.g. via `WholesalePricePerUnit`). Price codes will be ignored (e.g. `PriceType`). * Audius also does not support `ValidityPeriod` `EndDate`s. This includes using `EndDate` to specify multiple `ValidityPeriod`s with different prices. We can only parse 1 `ValidityPeriod` `StartDate`. * Audius currently only accepts the `Worldwide` `TerritoryCode`. Territory support for controlled streaming is coming soon! 🌎 * `NonInteractiveStream` is not supported. * A `ReleaseDeal` is required with a `DealReleaseReference` to each track on an album. * A `ReleaseDeal` is not required with a `DealReleaseReference` to an album release. If an album deal is not specified, the album defaults to being free to stream, but each of its tracks is configured according to the track’s `ReleaseDeal` `DealTerms`. * Audius supports updates via `NewReleaseMessage` and takedowns of content via `PurgeReleaseMessage` through matching `ReleaseId` properties. ## Recommended Deal Structure The following deal types are recommended for distribution to Audius. For complete documentation on supported deal types and corresponding XML, please see [Supported Deal Types](/distributors/specification/deal-types/supported-deal-types). If deals are provided without pricing information, the following standard pricing options are assumed: | Release type | Retail Price | Wholesale Price | | ------------ | ------------ | --------------- | | Track | $1.00 | $0.90 | | Album | $5.00 | $4.50 | :::warning[Note] Please note, advertisement and subscription model types are not supported. ::: ### 1. Paid Downloads with Full Length Streams Provide fans with a full-length streaming experience and an option to pay for a download of the work. ```xml FreeOfChargeModel OnDemandStream Worldwide 2023-09-02 PayAsYouGoModel PermanentDownload Worldwide 0.9 2023-09-02 ``` ### 2. Paid Downloads with Previewed Streams Provide fans with a previewed stream and a paid option to unlock the content. After purchasing, full-length streaming and downloads are unlocked. Default preview length is 30s of the track starting from 0s. If you would like to adjust the preview, see [Metadata](/distributors/specification/metadata) to provide the specific DDEX choreography. ```xml PayAsYouGoModel OnDemandStream PermanentDownload Worldwide 0.9 2023-09-02 ``` :::info[Further Reading] Checkout the [DDEX ERN3 Knowledge Base](https://kb.ddex.net/implementing-each-standard/electronic-release-notification-message-suite-\(ern\)/ern-3-explained/) for more information. ::: The following `Deal` types are supported for distribution to Audius. `Deal` types provided outside of the provided list will be ignored. If your use case extends beyond the supported `Deal` types outlined below, please contact `support@audius.co`. *** ### Tracks Audius accepts the following DDEX `Deal`s for **track** releases: #### Free To Stream 1. `CommercialModelType`: `FreeOfChargeModel` 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml FreeOfChargeModel OnDemandStream Worldwide 2023-09-02 ``` #### Pay Gated Stream 1. `CommercialModelType`: `PayAsYouGoModel` 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: any nonzero USD amount 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml PayAsYouGoModel OnDemandStream Worldwide 1.0 ... 2023-09-02 ``` #### Follow Gated Stream 1. `CommercialModelType`: `UserDefined` (`FollowGated`) 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml UserDefined OnDemandStream Worldwide 2023-09-02 ``` #### NFT Gated Stream 1. `CommercialModelType`: `UserDefined` (`NFTGated`) 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` 7. `Conditions` (custom XML specific to Audius) 1. For Ethereum NFTs: ```xml eth
// The Ethereum address of the NFT contract // The standard followed by the NFT - either "ERC-721" or "ERC-1155" // The name of the NFT // The slug of the NFT collection. E.g. if your collection is located at https://opensea.io/collection/example-nft, the slug is "example-nft". // Optional: URL to the image representing the NFT // Optional: URL to an external resource providing more details about the NFT
``` ii. For Solana NFTs: ```xml sol
// The address of the NFT on the Solana blockchain // The name of the NFT // Optional: URL to the image representing the NFT // Optional: URL to an external resource providing more details about the NFT
``` ```xml UserDefined OnDemandStream Worldwide 2023-09-02 eth
0xAbCdEfGhIjKlMnOpQrStUvWxYz
ERC-721 Example NFT example-nft https://www.example.com/nft-image.png https://www.example.com/nft-details
``` #### $AUDIO Tip Gated Stream 1. `CommercialModelType`: `UserDefined` (`TipGated`) 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml UserDefined OnDemandStream Worldwide 2023-09-02 ``` #### Free To Download :::info[Downloadable content is streamable.] If you can download it, you can stream it. ::: 1. `CommercialModelType`: `FreeOfChargeModel` 2. `UseType`: `Stream` or `OnDemandStream`, `PermanentDownload` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml FreeOfChargeModel OnDemandStream PermanentDownload Worldwide 2023-09-02 ``` #### Pay Gated Download :::info[Downloadable content is streamable.] If you can download it, you can stream it. ::: 1. `CommercialModelType`: `PayAsYouGoModel` 2. `UseType`: `Stream` or `OnDemandStream`, `PermanentDownload` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: any USD amount 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml PayAsYouGoModel OnDemandStream PermanentDownload Worldwide 1.0 ... 2023-09-02 ``` #### Follow Gated Download :::info[Downloadable content is streamable.] If you can download it, you can stream it. ::: 1. `CommercialModelType`: `UserDefined` (`FollowGated`) 2. `UseType`: `Stream` or `OnDemandStream`, `PermanentDownload` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml UserDefined OnDemandStream PermanentDownload Worldwide 2023-09-02 ``` *** ### Albums Audius accepts the following DDEX `Deal`s for **album** releases #### Free To Stream 1. `CommercialModelType`: `FreeOfChargeModel` 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml FreeOfChargeModel OnDemandStream Worldwide 2023-09-02 ``` #### Pay Gated Stream 1. `CommercialModelType`: `PayAsYouGoModel` 2. `UseType`: `Stream` or `OnDemandStream` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: any nonzero USD amount 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml PayAsYouGoModel OnDemandStream Worldwide 1.0 ... 2023-09-02 ``` #### Free To Download :::info[Downloadable content is streamable.] If you can download it, you can stream it. ::: 1. `CommercialModelType`: `FreeOfChargeModel` 2. `UseType`: `Stream` or `OnDemandStream`, `PermanentDownload` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: N/A 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml FreeOfChargeModel OnDemandStream PermanentDownload Worldwide 2023-09-02 ``` #### Pay Gated Download :::info[Downloadable content is streamable.] If you can download it, you can stream it. ::: 1. `CommercialModelType`: `PayAsYouGoModel` 2. `UseType`: `Stream` or `OnDemandStream`, `PermanentDownload` 3. `PriceType`: not supported 4. `WholesalePricePerUnit`: any USD amount 5. `ValidityPeriod` 1. `StartDate`: any 6. `TerritoryCode`: `Worldwide` ```xml PayAsYouGoModel OnDemandStream PermanentDownload Worldwide 1.0 ... 2023-09-02 ``` *** ### ERN4 Support :::info[Coming Soon] Support for ERN4 is coming coming soon. Checkout the [DDEX ERN4 Knowledge Base](https://kb.ddex.net/implementing-each-standard/electronic-release-notification-message-suite-\(ern\)/ern-4-explained/) for information in the meantime. ::: *** :::info Content can be ingested into the Audius network by directly operating your own DDEX ingestion server. More details soon. ::: :::info[Coming Soon!] Under construction. Please use as your own risk! 🚧 Check back soon for updates on how to run and operate own DDEX Ingestion Server. ::: ### Instructions 1. Provision a new machine following. We recommend: * Ubuntu * 8 CPUs * 16 to 32GB Memory 2. Visit [audius-docker-compose](https://github.com/AudiusProject/audius-docker-compose) and run the install script to set up your Audius environment on your provisioned machine. 3. Follow the instructions in the [apps](https://github.com/AudiusProject/apps/tree/main/packages/ddex) repository to set the appropriate environment variables 4. Start the DDEX Ingestion Server ``` audius-cli launch ddex ``` Your server will now be available at localhost:80 and is exposed via self-signed SSL on :443 if `DDEX_URL` is set. :::tip[Looking for how to accept deliveries from your label or distributor?] Check the [Audius Support Page](https://support.audius.co) for more information. ::: Audius supports bulk content ingestion via the DDEX standard. Providers, labels & partners with their own delivery infrastructure should reference the following documentation for guidance on how to send and administer content on Audius. If you're looking to build your own ingestion tooling, get started with the [Audius SDK](/sdk). ### What is DDEX? Digital Data Exchange (or "DDEX") is an international standards-setting organization that was formed in 2006 to develop standards that enable companies to communicate information along the digital supply chain more efficiently by: * Developing standard message and file formats (XML or flat-file) * Developing choreographies for specific business transactions * Developing communication protocols (SFTP or based on web services) * Working with industry bodies to create a more efficient supply chain :::info[More Information] - Learn more on the official DDEX Website here: [https://ddex.net/](https://ddex.net/) - Looking for a deeper technical dive? Checkout the DDEX knowledge base here: [https://kb.ddex.net/](https://kb.ddex.net/) ::: *** ### Open Source All DDEX ingestion code & libraries are open source and available on [GitHub](https://github.com/AudiusProject/apps/tree/main/packages/ddex). You may clone and self-operate your own Audius compatible DDEX ingestion server, which provides a web interface to deliver files, manage uploads, and track success. Under the hood, DDEX ingestion uses the [Audius SDK](/sdk) to process and upload tracks and is available in a [self-service](/distributors/self-serve/overview) manner. **Example files** Example DDEX XML files are available on [GithHub](https://github.com/AudiusProject/apps/tree/main/packages/ddex/processor/fixtures). * [Delivery](https://github.com/AudiusProject/apps/blob/main/packages/ddex/processor/fixtures/01_delivery.xml) * [Update](https://github.com/AudiusProject/apps/blob/main/packages/ddex/processor/fixtures/02_update.xml) * [Delete (Purge Message)](https://github.com/AudiusProject/apps/blob/main/packages/ddex/processor/fixtures/03_delete.xml) *** ### Partner Onboarding When delivering content to Audius, there are two options: 1. Run open source DDEX ingestion tooling yourself, starting [here](/distributors/self-serve/overview). 2. Work directly with a partner that will accept standard DDEX XML files via private delivery (S3, SFTP) and upload releases to Audius ##### Tiki Labs, Inc. Tiki Labs, Inc., one of the development teams behind the Audius protocol, offers a first-party service to run DDEX ingestion tooling on behalf of partners. In order to get started with Tiki Labs, Inc. directly, reach out to [ddex-support@audius.co](mailto\:ddex-support@audius.co) and submit an [intake form](https://forms.gle/jCwLLWRJY7fCQM5ZA). After on-boarding, you will deliver files directly to a provided S3 or SFTP bucket, which will be pushed to Audius as release criteria are met. * **DPID**: PA-DPIDA-202401120D-9 * **Party Name**: Tiki Labs, Inc. {/* TODO: when ready, add `npx create-audius-app` code block for rapid start */} ### 🧰 Use the Javascript SDK > Interact with music and accounts using the JavascriptSDK. * [Getting Started](/sdk) - Start here to get up and building with Audius. * [Create Apps Easily](/developers/guides/create-audius-app) - Run `npx create-audius-app` to get a head start building. * [Advanced Options](/sdk) - Ready to dig in more? check out the SDK docs for all available options. *** ### 📤 Use the REST API > Get read-only access using the RESTful API. * [Full API Reference](/api) - Query, stream, and search for tracks, users & playlists across the network. * [Explore the API with Postman](https://www.postman.com/samgutentag/workspace/audius-devs/collection/17755266-71da9172-77a7-427f-8ab5-1ce58f929ff5?action=share\&creator=17755266) - Explore the Audius API with Postman. *** ### 🧑‍💻 Getting Help & Reporting Bugs > Join the developer community, get direct support, and file issues. * [Developer Discord](https://discord.com/invite/audius) - Join the community and head to the **Developers** section * [Get one on one support](https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1IRk54J0XtTCavF8PbFSIxbaHX5tnybxKFLqEPvAXWYMgqNN9D9a6iFa_p6zCfTXsPbK8zIkKU) - Book a meeting with Developer Relations. * [Report a bug or make a feature request](https://github.com/AudiusProject/apps/issues/new/choose) - Better yet, see a bug you have a fix for? Open a PR! *** ### 🔐 Log in With Audius * [Quickstart](/developers/guides/log-in-with-audius) - Log In with Audius to retrieve a user's Audius profile information and optionally get permission to perform actions on their behalf. * [Demo Apps](/developers/guides/log-in-with-audius#examples) - Log In examples using React or vanilla JS, and a convenient button generator to get started. *** ### 🦔 Hedgehog > Hedgehog is an open-source, client-side Ethereum wallet that aims to lower the barrier of entry to > crypto projects by using a username and password. * [Learn More](/developers/guides/hedgehog) - Learn more about the motivation for a lower barrier to entry wallet in the crypto ecosystem. * [Source Code](https://github.com/AudiusProject/hedgehog) - Get the source code on GitHub. * [More Docs](https://audiusproject.github.io/hedgehog-docs/#installation) - Even more Hedgehog specific documentation. *** ### ፨ Audius on The Graph > Explore on-chain governance using > [The Graph](https://thegraph.com/docs/about/introduction#what-the-graph-is). Audius has a GraphQL API Endpoint hosted by The Graph called a subgraph for indexing and organizing data from the Audius smart contracts. * [The Graph Guide](/developers/guides/subgraph) - Explore on-chain governance data using the Audius subgraph. :::warning[Work in progress] This page will not be published to builds. it is available when run locally only. ::: > content goes here ### Quick Start ```sh npx create-audius-app cd my-app npm run dev ``` *** ### Creating an App The easiest way to start building a new application on top of the Audius Protocol is by using `create-audius-app`. This CLI tool enables you to quickly start building a new Audius application, with everything set up for you. You can create a new app using the default Audius react template, or by using one of the [examples][example-repo]. :::info[Minimum Node Version] You’ll need to have Node >= 18 on your local development machine. You can use [nvm][nvm-url] (macOS/Linux) or [nvm-windows][nvm-windows-url] to switch Node versions between different projects. ::: To create a new app run the following command: ```sh npx create-audius-app ``` You will be asked for the name of your project, and all the necessary dependencies will be installed. {/* prettier-ignore */}
#### Non Interactive Mode To bypass the prompt for an app name, append it to the create command like this: ```sh npx create-audius-app my-first-audius-app ``` Explore other command line options by running `npx create-audius-app --help` #### Output Running `create-audius-app` will create a directory called `my-app` inside the current folder. Inside that directory, it will generate the initial project structure and install the transitive dependencies: ```sh my-app ├── README.md ├── gitignore ├── index.html ├── node_modules ├── package-lock.json ├── package.json ├── public ├── src │   ├── App.css │   ├── App.tsx │   ├── assets │   ├── emotion.d.ts │   ├── main.tsx │   └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ``` No configuration or complicated folder structures, only the files you need to build your app. Once the installation is done, you can open your project folder: ```sh cd my-app ``` ### Launch Your App Once inside the `my-app` directory run the following command and take note of the local host port: ```sh npm run dev ``` {/* prettier-ignore */}
This example uses port 5173 on localhost.
*** ### Design Applications started from `create-audius-app` leverage the [Harmony][harmony-docs] design system. Harmony is all about collaboration, reusability, and scalability. It aims to harmonize code and Figma, provides a shared language for designers and developers, and provide consistent, reusable components for use across platforms. Read more about Harmony and using it across your other projects at [https://harmony.audius.co/][harmony-docs]. {/* prettier-ignore */} [example-repo]: https://github.com/AudiusProject/apps/tree/main/packages/create-audius-app/examples [nvm-url]: https://github.com/nvm-sh/nvm#installation [nvm-windows-url]: https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows [harmony-docs]: https://harmony.audius.co/ **Programmable distribution** lets you control who can stream a track. Instead of making every track publicly streamable, you designate one or more wallet addresses as **access authorities**. Only requests signed by those addresses are accepted by the protocol. Your server holds the key and decides who gets access. ### How It Works When you create a track, you set `access_authorities` to the wallet address(es) that can authorize stream requests. Validator nodes enforce this: if a stream request is unsigned or signed by an address not in `access_authorities`, the node returns 401 and rejects it. Your **access server** holds the private key for one of those addresses. When a user requests a stream, your server: 1. Verifies the user is allowed (e.g. logged in, in the right region, has paid, follows you) 2. Fetches the stream URL from the Audius API 3. Signs a short-lived signature in the gate-release-access format 4. Redirects the user to the stream URL with the signature attached The node validates your signature, confirms the signer is in the track’s `access_authorities`, and serves the audio. Without your server’s signature, direct requests to the node fail. ### Access Authorities `access_authorities` is an array of Ethereum addresses. Any one of them can sign to authorize a stream. Common patterns: * **Single signer** — One address (e.g. your server’s wallet). Simplest and most common. * **Multiple signers** — Several addresses for redundancy or delegated access. * **Empty or omitted** — The track is public; no signature required. Tracks with `access_authorities` are **gated**. Tracks without it are **public** and can be streamed by anyone with the URL. ### Example: Gated Upload The [gated-upload example](https://github.com/AudiusProject/apps/tree/main/packages/web/examples/gated-upload) implements programmable distribution with geo-gating. **Server** * `POST /create-track` — Creates a track with `access_authorities: [signerAddress]`. The server’s wallet is the only authority. * `GET /stream/:trackId` — Checks the client’s IP via ip-api.com. If the client is in `ALLOWED_COUNTRIES`, fetches the track from the SDK, signs the stream URL, and redirects. Otherwise returns 403. * `GET /my-region` — Returns the client’s IP, country, city, and whether they’re allowed (for UI feedback). **Client** * OAuth login, upload via SDK (`uploadTrackFiles`, then `create-track` with the server), and streaming via `GET /stream/:trackId` (server redirects to signed URL). Run the server from `packages/web/examples/gated-upload/server` with `AUDIUS_API_KEY`, `AUDIUS_BEARER_TOKEN`, and `SIGNER_PRIVATE_KEY` in `.env`. See the [README](https://github.com/AudiusProject/apps/blob/main/packages/web/examples/gated-upload/README.md) for full setup. ### What You Can Build Programmable distribution supports many use cases where you want to gate streaming behind your own logic. #### Geo-Gated Releases Only allow streaming from certain countries. The gated-upload example uses ip-api.com to resolve IP → country and blocks requests outside `ALLOWED_COUNTRIES`. Useful for licensing, regional rollouts, or compliance. #### Private Groups Restrict streams to members of a private community. Your access server checks whether the user is logged in and in the group (e.g. Discord role, invite list, subscription). Only then does it sign the stream URL. #### Frontend-Gated Releases Limit streaming to users who arrive through your app or frontend. Your server can verify a session, referrer, or token before signing. Direct links from other sites fail without that check. #### Paid / Premium Content Require payment, subscription, or NFT ownership before signing. Your server verifies the purchase or membership and signs only for eligible users. #### Time-Based or Schedule-Based Access Release content at a specific time or after a countdown. Your server checks the current time or event state before signing. ### Summary | Concept | Meaning | | ----------------------- | ----------------------------------------------------------------- | | **access\_authorities** | Wallet addresses that can sign to authorize stream access | | **Gated track** | Has `access_authorities`; requires a valid signature to stream | | **Public track** | No `access_authorities`; anyone can stream | | **Access server** | Your backend that holds the signing key and enforces access logic | | **gate-release-access** | Signature format the protocol expects on stream URLs | For implementation details (signature format, canonical JSON, EIP-191 hashing), see the [Open Audio Protocol gate-release-access tutorial](https://github.com/AudiusProject/open-audio-docs/blob/main/docs/pages/tutorials/gate-release-access.mdx) and the [gated-upload server source](https://github.com/AudiusProject/apps/blob/main/packages/web/examples/gated-upload/server/server.js). > Build DApps Like Apps *** Hedgehog is an open-source, client-side Ethereum wallet that uses a username and password. It aims to lower the barrier of entry to crypto projects for non tech-savvy users. Allow users to interact with your DApp just like they would any other website, no extensions required, without centralizing control of keys. Hedgehog is an alternative to Metamask that manages a user's private key and wallet on the browser. It exposes a simple API to allow you to create an authentication scheme to let users sign up and login to their wallet across multiple browsers and devices. *** ### Not All Transactions Are Created Equal Decentralized apps today require lots of technical knowledge to configure and use, limiting your user base and reducing the potential for growth. Currently available wallets treat every transaction as if it were moving around your life’s savings. Hedgehog was built for use cases involving low-to-no financial value. *** ### Is Hedgehog Right for your DApp? :::note The primary improvement to end-user experience is gained by hiding wallet complexity and not forcing users to constantly confirm transactions - The opposite of what you’d want when moving significant money around. ::: Hedgehog isn’t right for every DApp. Massive improvements in user experience are only possible through tradeoffs. As a general rule Hedgehog should not be used for apps involving significant sums of money. As a bridge, one could start users on Hedgehog and suggest migrating to a more secure wallet if their stored value increases beyond a certain threshold; the Hedgehog paradigm is interoperable with existing web3 providers too. #### Good Use Cases * **Signing data** - If you’re building decentralized applications that rely on user signed data (eg. via EIP-712-esque signing schemes), Hedgehog could help simplify the experience if the stakes are low enough. * **Gaming DApp** - Nothing ruins fun as much as signing transactions. If you’re building a gaming DApp that doesn’t use significant financial assets, improving UX is key. * **Decentralized Music Player** - If you’re building consumer-facing DApps, Hedgehog will dramatically improve user experience and significantly increase your potential userbase. #### Not So Good Use Cases If your DApp involves moving around significant sums of money, then the tradeoff in security is most likely not worth it. Hedgehog’s primary improvement to end-user experience is by hiding the wallet and not forcing users to confirm transactions - The opposite of what you’d want when moving money around. We absolutely don’t recommend using Hedgehog in situations like these: * Banking DApp * Decentralized Lending * Prediction Markets *** ### A Closer Look Hedgehog is a package that lives in your front end application to create and manage a user's entropy (from which a private key is derived). Hedgehog relies on a username and password to create auth artifacts, so it's able to simulate a familiar authentication system that allows users to sign up or login from multiple browsers or devices and retrieve their entropy. Those artifacts, through hedgehog, are persisted to a backend of your choosing. :::note A private key is only computed and available client side and is never transmitted or stored anywhere besides the user's browser. ::: ```javascript // Provide getFn, setAuthFn, setUserFn as requests to your database/backend service (more details in docs). const hedgehog = new Hedgehog(getFn, setAuthFn, setUserFn) let wallet if (hedgehog.isLoggedIn()) { wallet = hedgehog.getWallet() } else { wallet = await hedgehog.login('username', 'password') // or wallet = await hedgehog.signUp('username', 'password') } ``` After creating or retrieving a user's wallet, you can either **fund their wallet directly** to pay transaction fees or **relay their transactions through a EIP-712 relayer**. *** ### Installation ```bash npm i --save @audius/hedgehog ``` *** ### Docs & Examples For a quick browser-side demo, [look no further](https://codesandbox.io/embed/pp9zzv2n00). For a full end-to-end auth demonstration, see our [demo repo](https://github.com/AudiusProject/audius-hedgehog-demo). Ready to learn more? [Take a deeper dive into the docs](/api) and find the source code on [Github](https://github.com/AudiusProject/hedgehog). Images (artwork, profile pictures, cover photos) served by Audius are replicated across validator nodes. When an image fails to load—due to node unavailability or network issues—your app should retry using alternate mirror hosts. Without mirror fallback, image loading is unreliable. ### API Response Structure Artwork and profile image objects in API responses include size variants **and** a `mirrors` array: ```json { "artwork": { "150x150": "https://audius-content-7.cultur3stake.com/content/Qmd9Z9BS6NAGASFWcTdk1bhSaiJR84czJXeKNgLcL7hH4L/150x150.jpg", "480x480": "https://audius-content-7.cultur3stake.com/content/Qmd9Z9BS6NAGASFWcTdk1bhSaiJR84czJXeKNgLcL7hH4L/480x480.jpg", "1000x1000": "https://audius-content-7.cultur3stake.com/content/Qmd9Z9BS6NAGASFWcTdk1bhSaiJR84czJXeKNgLcL7hH4L/1000x1000.jpg", "mirrors": [ "https://audius-creator-6.theblueprint.xyz", "https://cn0.mainnet.audiusindex.org", "https://creatornode2.audius.co" ] } } ``` * **Size variants** (`150x150`, `480x480`, `1000x1000`): Use the variant closest to the displayed size for performance. * **mirrors**: Alternate validator node host roots. Mirror order is arbitrary; try each until one succeeds. Profile objects use the same pattern for `profile_picture` and `cover_photo` (with `_150x150`, `_480x480`, `_1000x1000` and `mirrors` in some adapters). ### Mirror Fallback Strategy When an image URL fails to load: 1. Take the current URL (e.g. from a size variant). 2. Replace the host (scheme + authority) with each mirror root, in order. 3. Try each resulting URL until one loads successfully or all are exhausted. 4. Optionally fall back to a placeholder or `onError` handler afterward. Example host-swap logic (conceptual): ```js // Given: originalUrl, mirrors = ["https://cn0.mainnet.audiusindex.org", ...] function buildMirrorUrl(originalUrl, mirrorHost) { const url = new URL(originalUrl) url.host = new URL(mirrorHost).host return url.toString() } ``` ### Best Practices #### 1. Preserve mirrors in normalization Do not reduce artwork or profile objects to a single URL string during normalization. If you do, mirrors are lost and cannot be used for retries. Keep `mirrors` attached to the image metadata that your image component receives. #### 2. Use a shared image component with mirror retry Avoid raw `` for Audius content. Centralize mirror-aware loading in one component (e.g. `RetryImage`, `ArtworkImage`) that: 1. Tries the primary URL. 2. On `onError`, retries with each mirror (by swapping host). 3. Falls back to `fallbackSrc` or `onError` only after all mirrors fail. #### 3. Apply consistently everywhere Mirror retry must be used for **all** Audius images—track art, playlist art, profile pictures, cover photos, etc. Partial adoption (e.g. only where `RetryImage` is used) leaves gaps and broken images in other views. #### 4. Use size-aware variant selection Pick the size variant closest to the displayed dimensions (and device pixel ratio) to avoid loading oversized images. Register or pass all variants plus mirrors so the component can retry with the same size on different hosts. ### Common Pitfalls | Pitfall | Consequence | | --------------------------------------------- | ---------------------------------------------------------- | | `getArtworkUrl()` returning only a single URL | Mirrors are dropped; no retry possible. | | Raw `` instead of mirror-aware component | No retry on failure. | | Mirror logic only in some components | Inconsistent behavior; images fail in non-covered screens. | | Ignoring mirrors in API responses | Same as above; no fallback hosts available. | ### Reference Implementation The Audius embed player uses mirror fallback in `getArtworkUrl`. See [getArtworkUrl.js](https://github.com/AudiusProject/apps/blob/main/packages/embed/src/util/getArtworkUrl.js) in the apps repo.: it preloads the primary URL, and on failure, swaps the host with each mirror and retries before returning. For React apps, implement or use a shared component that accepts the full artwork/profile object (with variants and mirrors) and performs the same retry logic on load failure. > Help other users identify you by connecting your [Audius][audius-co] account to the [Audius > Protocol Dashboard][protocol-dashboard]. Once you've linked your Audius account, your Profile Picture and Display Name will be visible to users throughout the protocol dashboard. ### Connect to Protocol Dashboard 1. Navigate to the [Audius Protocol Dashboard][protocol-dashboard] 2. Click the "Connect Wallet" button on the upper right {/* prettier-ignore */}
Wallet Connect Button
3. Select your web3 wallet in the wallet selection modal and sign in. {/* prettier-ignore */}
Wallet Selection Modal
4. By default, a Gravatar style icon will be used to represent the wallet across the Dashboard. {/* prettier-ignore */}
Protocol Dashboard default profile icon
*** ### Connect to Audius Profile To connect your Audius profile to the Dashboard, 1. Click the "Connect Audius Profile" button in the upper right corner. {/* prettier-ignore */}
Protocol Dashboard "Connect Audius Profile" button
2. In the modal, confirm your understanding and proceed by clicking the "Connect Profile" button. {/* prettier-ignore */}
Review and confirm the next step by clicking "Connect Profile" in the modal.
3. In the pop over, enter the credentials of the Audius account you want to connect to your Protocol Dashboard account and click "Sign In & Authorize App"
Sign in with your Audius account to continue
:::tip[Already signed in?] If you are already signed in to an Audius account it will be chosen as the default. If you would like to use a different Audius account, be sure to sign out and sign in with the correct account before clicking "Authorize App". If you are not currently signed in to an Audius account, you will be prompted to do so. ::: 4. Your wallet app will present a signature request to confirm the account connection. Sign this message to complete the link. {/* prettier-ignore */}
Using MetaMask as an example, sign the request.
5. Complete! Now your Audius account profile image will be shown on the Audius Protocol Dashboard! {/* prettier-ignore */}
Account icon when an Audius account is connected to the Protocol Dashboard.
{/* prettier-ignore */} [audius-co]: https://audius.co/ [protocol-dashboard]: https://dashboard.audius.org/

Log In with Audius popup

UI for read and read/write authorization flows

Log In with Audius lets you retrieve a user's Audius profile information and optionally get permission to perform actions on their behalf, without making the user give you their Audius password. ### Quickstart ```html title="index.html"
``` ```js title="script.js" showLineNumbers import Web3 from 'web3' import { sdk } from '@audius/sdk' window.Web3 = Web3 const audiusSdk = sdk({ apiKey: 'Your API key goes here' }) audiusSdk.oauth.init({ successCallback: (userInfo) => { console.log('Log in success!', userInfo) }, }) audiusSdk.oauth.renderButton({ element: document.getElementById('audiusLogInButton'), scope: 'read', // use "write" instead if you'd like to request read/write access to user's account }) ``` :::tip Don't have an API key? Get one by creating a developer app on the Audius [Settings page](https://audius.co/settings). ::: ### Examples * Demo with React - [Demo app](https://nffqd5.csb.app/) | [Code](https://codesandbox.io/s/log-in-with-audius-demo-723-nffqd5?file=/package.json:170-183) * Demo with vanilla JS - [Demo app](https://f68xgn.csb.app/) | [Code](https://codesandbox.io/s/log-in-with-audius-demo-vanilla-js-723-f68xgn?file=/index.html) * [Log In Button Generator](https://9ncjui.csb.app/) ### Full Reference #### 1. Install the JavaScript SDK :::note If you are not able to use the JavaScript SDK (for example, if you are developing a mobile app), skip to [Manual Implementation](#manual-implementation). ::: In your terminal, run the following: ```bash npm install web3 @audius/sdk ``` Then, initialize the SDK: ```js import Web3 from 'web3' import { sdk } from '@audius/sdk' window.Web3 = Web3 const audiusSdk = sdk({ apiKey: 'Your API Key goes here' }) ``` :::tip See complete instructions [here](/sdk#install-the-sdk) to install and initialize the JavaScript SDK. ::: #### 2. Initialize the SDK `oauth` feature ```js showLineNumbers audiusSdk.oauth.init({ successCallback: (res) => { // This will run if the user logged in successfully. console.log('Log in success!', res) /** `res` will contain the following user information: { userId: number; // unique Audius user identifier email: string; name: string; // user's display name handle: string; verified: boolean; // whether the user has the Audius "verified" checkmark profilePicture: {"150x150": string, "480x480": string, "1000x1000": string } | null // URLs for the user's profile picture apiKey: string | null // the API key for your application if specified sub: number; // alias for userId iat: string; // timestamp for when auth was performed } **/ }, errorCallback: (err) => { // This will run if there was an error during the auth flow. console.log('Error :(', err) // `err` will contain the error message }, }) ``` #### 3. Render the Log In button ```js title="In your JS" audiusSdk.oauth.renderButton({ element: document.getElementById('audiusLogInButton'), scope: 'read', // Change to "write" if you need write access to users' accounts buttonOptions: { size: 'large', corners: 'pill', customText: 'Continue with Audius', }, }) ``` ```html title="In your HTML"
``` [`renderButton`](/sdk/oauth#renderbutton) replaces the given `element` with the Log In with Audius button. If `scope` is set to `"write"`, the user will be prompted to grant your app read/write access to their account (allowing your app to perform actions like uploading a track on the user's behalf). If `scope` is set to `"read"`, the user will be prompted to grant your app read-only access to their account. :::note The `write` scope grants your app permission to perform most actions on the user's behalf, but it does NOT allow any access to DMs or wallets. ::: You can also pass in `buttonOptions`, an optional object with customization settings for the button. :::tip Use [this playground](https://9ncjui.csb.app/) to explore the different button options. ::: If you don't want to use `renderButton`, you can implement a login button yourself and invoke the login popup with [`audiusSdk.oauth.login`](/sdk/oauth#login).
Optional: Show loader until the button is ready The button may take up to a couple of seconds to load. You may want to show a loading indicator until the button has loaded for an optimal user experience. ```html title="In your HTML" showLineNumbers {/* Surround your element that will be replaced with the Log In with Audius button with a parent, e.g.: */}
Loading...
``` ```javascript title="In your JS" showLineNumbers const observer = new MutationObserver(function (mutations_list) { mutations_list.forEach(function (mutation) { mutation.addedNodes.forEach(function (added_node) { if (added_node.id == 'audius-login-button') { // Login button has rendered document.querySelector('#loading').remove() observer.disconnect() } }) }) }) observer.observe(document.querySelector('#parent'), { subtree: false, childList: true, }) ``` The log in button will be rendered with an id of `audius-login-button`. As shown above, you can detect when the element has been added using a MutationObserver.
#### 5. Optional: Write on behalf of the Audius user Once a user has authorized your app with the `write` scope, you can easily perform actions on their behalf using the SDK. Simply initialize the SDK with your API Key and Secret and begin using the various write methods. ```js title="Server-side JS" const audiusSdk = sdk({ apiKey: 'Your API Key goes here', apiSecret: 'Your API Secret goes here', }) const track = await audiusSdk.tracks.favoriteTrack({ trackId: 'D7KyD', userId: 'Audius user ID of user who gave your app write access', }) ``` :::warning Be careful to not expose API secrets on your frontend! ::: #### 6. Done! * [See examples](#examples) * [Read full SDK `oauth` docs](/sdk/oauth) * [Explore the API docs](/sdk/tracks) ::::note #### Retrieving email addresses Once you know your user's Audius user id, you can retrieve their Audius information at any time using our SDK or web APIs. However, the one piece of profile information that is not available outside of the Log In with Audius response is the user's email address. If you do not initially store the user's email address, you can only re-retrieve the email through having the user re-complete the Log In with Audius flow. ::: ### Example use cases ##### Write scope * Upload tracks to your users' Audius accounts * Save tracks to your users' Audius libraries ##### Read-only scope * Provide a convenient way for users to sign up and/or log in to your app without having to set a password or fill in a profile form * Associate a user to their Audius account so that you can retrieve their Audius data (e.g. retrieve their tracks) * Confirm if a user is a "Verified" Audius artist However, note that this flow **CANNOT**: * Manage the user's login session on your app ### Workflow The "Log In with Audius" flow looks like this: 1. You provide a button on your app or website to begin the authentication flow 2. When the user clicks the button, it opens the Log In with Audius page 3. Once the user successfully authorizes your app, Audius provides your app/website with the user profile using a JSON Web Token (JWT) 4. Your app verifies and decodes the JWT The JWT payload contains the following information about the user: * Unique identifier (Audius user id) * Email * Display name * Audius handle * Whether the user is a verified artist (i.e. has a purple checkmark) * Profile picture URL ### Manual Implementation If you are not able to use the Audius JavaScript SDK, you may implement Log In with Audius manually by following the steps below. #### 1. Open the Log In with Audius prompt page Create a "Log In with Audius" button on your app. :::tip If using HTML (or HTML-like markup) and CSS, check out [this playground](https://j2jx6f.csb.app/) to easily customize and generate code for an Audius-branded login button. ::: :::: Clicking your log in button should direct the user to the Log In with Audius prompt page. On a native app, the log in button should open a secure web browser within the app that loads the Audius login page. A web app should open the Audius login page in a popup or redirect to it. The Log In with Audius prompt page is located at the following URL: `https://audius.co/oauth/auth?scope={read|write}&api_key={Your API Key}&redirect_uri={Your Redirect URI}&origin={Your App Origin}&state={Your State Value}&response_mode={query|fragment}` You must open this page with the required URL parameters, described below. **Params** * scope `"read" | "write"` - the scope of the authentication request. Use `"write"` if your app will request read/write access to the user's account; otherwise, use `"read"` if your app only needs read access. * api\_key `string` - your app's Audius API Key. If you don't have one, you can create one on the Audius [Settings page](https://audius.co/settings). * redirect\_uri `string` - the location that the Audius login page should redirect to once the user successfully authenticates. A URL protocol of http or https is required. You can use the special value `postmessage` here if you would like the login page to send the response back to its opener using `window.postMessage` instead of using a redirect. Otherwise, the following validation rules apply: * Hosts cannot be raw IP addresses UNLESS they are localhost IP addresses * Cannot contain the fragment component (`#`) * Cannot contain the `userinfo` component * Cannot contain a path traversal (contain `/..` or `\..`) * Must contain valid characters and URI format * origin *optional* `string` - only applicable and required if `redirect_uri` is set to `postmessage`. If so, this value should be set to the [origin](https://developer.mozilla.org/en-US/docs/Web/API/URL/origin) of the window that opened the Log In with Audius popup. * state *optional but highly recommended* `string` - any string. When the user is redirected back to your app, the exact `state` value you provide here will be included in the redirect (in the `state` URI fragment parameter). **This field should be leveraged as a CSRF protection mechanism** (read more [here](https://auth0.com/docs/secure/attack-protection/state-parameters) or [here](https://security.stackexchange.com/questions/20187/oauth2-cross-site-request-forgery-and-state-parameter)), and may also be used as a way to persist any useful data for your app between where the `state` value is generated and where the redirect goes. * `response_mode` *optional, not recommended when possible* `"fragment" | "query"` - specifies whether the auth flow response parameters will be encoded in the query string or the fragment component of the redirect\_uri when redirecting back to your app. Default behavior is equivalent to "fragment". We recommend NOT changing this if possible. * `display` *optional* `"popup" | "fullScreen"` - determines whether the auth flow expects to be rendered in a popup or a full screen view. Defaulted to `"popup"` if unspecified. **Example** ```html noInline Click me to log in with Audius! ``` :::tip[Remember to handle early exiting (i.e. failure) of the authentication flow] If the user exits the authentication flow before completing it--e.g. by closing the window--your app should detect this and have the UI respond accordingly. ::: #### 2. Receive the response ##### **If you used a redirect URI**: When the user has successfully authenticated, the Log In with Audius page will redirect to the redirect URI that you specified, **with 1) the JWT containing the user profile, and 2) the original state value you provided (if any) included in the URI fragment** (or query string, if `response_mode` was set to `query`). To illustrate, going off the example above where we opened the login page with the following URL: ```noInline https://audius.co/oauth/auth?scope=read&app_name=My%20Demo%20App&redirect_uri=https://mydemoapp.com/oauth/receive-token&state=a4e0761e-8c21-4e20-819d-5a4daeab4ea9 ``` when the user successfully authenticates, the login page would redirect to: `https://mydemoapp.com/oauth/receive-token#state=a4e0761e-8c21-4e20-819d-5a4daeab4ea9&token={JWT}` where `{JWT}` is a [JSON web token](https://jwt.io/introduction) containing the user's encoded profile information and a signature. :::info[If you specified `response_mode=query` when opening the login page, the login page would] instead redirect to...: `https://mydemoapp.com/oauth/receive-token?state=a4e0761e-8c21-4e20-819d-5a4daeab4ea9&token={JWT}` ::: Skip to [**Handling the response**](#3-verify-the-response) below for what to do next. ##### **If you used `redirectURI=postmessage`**: When the user has successfully authenticated, the Log In with Audius page will send a message via `window.postMessage` to the window that opened it. The message will contain a JWT containing the user profile as well as whatever `state` value you originally specified in the corresponding URL param, if any. For instance, if your app opened the login page using the following URL: ```noInline https://audius.co/oauth/auth?scope=read&app_name=My%20Demo%20App&redirect_uri=https://mydemoapp.com/oauth/receive-token&state=a4e0761e-8c21-4e20-819d-5a4daeab4ea9 ``` the message would look like this: ``` { token: , state: 'a4e0761e-8c21-4e20-819d-5a4daeab4ea9' } ``` where `` is a [JSON web token](https://jwt.io/introduction) containing the user's encoded profile information and a signature. :::warning Make sure that your `postMessage` event listener validates that the origin of the incoming event is `https://audius.co`! ::: #### 3. Verify the response Extract the JWT (`token`) from the URI fragment or query string (if you used a redirect) or the event message (if you used `postmessage`). Once you have the token, you must send it to the Audius `verify_token` endpoint in order to verify that: * the signature was signed by the Audius user who completed the authentication * the content of the token hasn't been tampered with Upon verifying the validity of the JWT, the endpoint will return the authenticated user's decoded profile information. GET /v1/users/verify\_token?token=\[JWT] **Params** * token `string` - the JWT from the authentication flow that you would like to verify **Sending the request** To use the API, you first select an API endpoint from the list of endpoints returned by: `GET https://api.audius.co` Once you've selected a host, all API requests can be sent directly to it. For instance, if you picked the host at `https://audius-dp.singapore.creatorseed.com`, you would send the GET request to `https://audius-dp.singapore.creatorseed.com/v1/users/verify_token?token=`, where `` is replaced with the JWT you retrieved in the auth flow. We recommend selecting a host each time your application starts up as availability may change over time. **Success response** * Code: 200 OK * Content: The decoded JWT payload, which contains the user's profile information: ```ts { userId: number, // unique Audius user identifier email: string, name: string, // user's display name handle: string, verified: boolean, // whether the user has the Audius "verified" checkmark profilePicture: {"150x150": string, "480x480": string, "1000x1000": string } | null // URLs for the user's profile picture apiKey: string | null // the API key for your application if specified sub: number, // alias for userId iat: string, // timestamp for when auth was performed } ``` **Error responses** Invalid signature: * Code: `404` Not Found * Content: Error message describing the issue that occurred, e.g. "The JWT signature is invalid - wallet could not be recovered." Problem with `token` input: * Code: `400` Bad Request * Content: Error message, e.g. "the JWT signature could not be decoded." ##### 3. Done! Once you've verified the JWT, the authentication flow is complete and you now have your user's Audius profile information. If you used the `write` scope, you can use the Audius SDK to perform actions on behalf of the user who authorized your app: ```js title="Server-side JS const audiusSdk = sdk({ apiKey: 'Your API Key goes here', apiSecret: 'Your API Secret goes here', }) const track = await audiusSdk.tracks.favoriteTrack({ trackId: 'D7KyD', userId: 'Audius user ID of user who gave your app write access', }) ``` See [Getting Started](/sdk) with the SDK or [the SDK methods reference](/sdk/tracks) for further reading. ##### [A quick note on email](#retrieving-email-addresses) Audius has a GraphQL API Endpoint hosted by [The Graph](https://thegraph.com/docs/about/introduction#what-the-graph-is) called a subgraph for indexing and organizing data from the mainnet Ethereum contracts. This subgraph is can be used to query Audius data. Subgraph information is serviced by a decentralized group of server operators called Indexers. *** ### Ethereum Mainnet [Creating an API Key Video Tutorial](https://www.youtube.com/watch?v=UrfIpm-Vlgs) * [Explorer Page](https://thegraph.com/explorer/subgraphs/F8TjrYuTLohz64J8uuDke9htSR1aY9TGCuEjJVVjUJaD?view=Query\&chain=arbitrum-one) * Graphql Endpoint: `https://gateway.thegraph.com/api/[api-key]/subgraphs/id/F8TjrYuTLohz64J8uuDke9htSR1aY9TGCuEjJVVjUJaD` * [Code Repo](https://github.com/AudiusProject/audius-subgraph) *** ### Helpful Links * [Querying from an Application](https://thegraph.com/docs/en/subgraphs/querying/introduction/) * [Managing your API Key & Setting your indexer preferences](https://thegraph.com/docs/en/studio/managing-api-keys/) ### Sample Queries Below are some sample queries you can use to gather information from the Audius contracts. You can build your own queries using a [GraphQL Explorer](https://graphiql-online.com/graphiql) and enter your endpoint to limit the data to exactly what you need. *** #### User Description: Get users balance of claimable stake and delegation information. ```graphql { user(id: "0x8c860adb28ca8a33db5571536bfcf7d6522181e5") { balance totalClaimableAmount claimableStakeAmount claimableDelegationSentAmount claimableDelegationReceivedAmount stakeAmount delegationSentAmount delegationReceivedAmount deployerCut delegateTo(orderBy: claimableAmount, orderDirection: desc) { amount claimableAmount toUser { id } } delegateFrom(orderBy: claimableAmount, orderDirection: desc) { amount claimableAmount fromUser { id } } } } ``` *** #### Audius Network Description: Find minimum stake and maximum on delegation ```graphql { audiusNetworks(first: 5) { id audiusTokenAddress claimsManagerAddress delegateManagerAddress } serviceTypes(first: 5) { id isValid minStake maxStake } } ``` ### Entities Description: | Field | Type | Description | | --------------------------------- | ------ | ------------------------------------------------------------------------------------------------- | | `id` | ID | ID is set to 1 | | `audiusTokenAddress` | Bytes | audiusToken address | | `claimsManagerAddress` | Bytes | claimsManager address | | `delegateManagerAddress` | Bytes | delegateManager address | | `governanceAddress` | Bytes | governance address | | `registry` | Bytes | registry address | | `serviceProviderFactoryAddress` | Bytes | serviceProviderFactory address | | `serviceTypeManagerAddress` | Bytes | serviceTypeManager address | | `stakingAddress` | Bytes | staking address | | `registryAddress` | Bytes | registry address | | `totalSupply` | BigInt | Total supply of $AUDIO | | `totalAUDIOMinted` | BigInt | Total amount of $AUDIO minted | | `totalAUDIOBurned` | BigInt | Total amount of $AUDIO burned | | `totalTokensStaked` | BigInt | Total amount of $AUDIO staked | | `totalTokensClaimable` | BigInt | Total tokens that are settled and claimable | | `totalTokensLocked` | BigInt | Total tokens that are currently locked or withdrawable in the network from unstaking/undelegating | | `totalTokensDelegated` | BigInt | Total delegated tokens in the protocol | | `maxDelegators` | BigInt | The max number of delegators per service provider | | `inDelegationAmount` | BigInt | The minimum amount needed to delegate | | `undelegateLockupDuration` | BigInt | The minimum number of blocks the user must wait from requesting undelegation to evaluating | | `removeDelegatorLockupDuration` | BigInt | The minimum number of blocks the user must wait from requesting remove delegator to evaluating | | `removeDelegatorEvalDuration` | BigInt | Evaluation period for a remove delegator request | | `decreaseStakeLockupDuration` | BigInt | Number of blocks a decrease stake request is in lockup before evaluation is allowed | | `updateDeployerCutLockupDuration` | BigInt | Number of blocks an update deployer cut request is in lockup before evaluation is allowed | | `fundingRoundBlockDiff` | BigInt | | | `fundingAmount` | BigInt | | | `recurringCommunityFundingAmount` | BigInt | | | `communityPoolAddress` | Bytes | address | | `votingQuorumPercent` | BigInt | | | `votingPeriod` | BigInt | | | `executionDelay` | BigInt | | | `maxInProgressProposals` | Int | | | `guardianAddress` | Bytes | | | `requestCount` | BigInt | | | `totalStaked` | BigInt | | *** ### ClaimEvent Description: | Field | Type | Description | | ------------- | ------ | ----------- | | `id` | ID | | | `claimer` | User | | | `rewards` | BigInt | | | `newTotal` | BigInt | | | `blockNumber` | BigInt | | *** ### ClaimProcessedEvent Description: | Field | Type | Description | | ------------- | ------ | ----------- | | `id` | ID | | | `rewards` | BigInt | | | `claimer` | User | | | `oldTotal` | BigInt | | | `newTotal` | BigInt | | | `blockNumber` | BigInt | | *** ### ClaimRound Description: | Field | Type | Description | | ------------- | ------ | ---------------- | | `id` | ID | The round number | | `fundAmount` | BigInt | | | `blockNumber` | BigInt | | *** ### DecreaseStakeEvent Description: | Field | Type | Description | | -------------------- | ------------ | ----------- | | `id` | ID | | | `status` | LockupStatus | | | `owner` | User | | | `expiryBlock` | BigInt | | | `createdBlockNumber` | BigInt | | | `endedBlockNumber` | BigInt | | | `decreaseAmount` | BigInt | | | `newStakeAmount` | BigInt | | *** ### Delegate Description: | Field | Type | Description | | ----------------- | ------ | ---------------------------------------------------------------- | | `id` | ID | ID - generated w/ the service provider's & delegator's addresses | | `id` | ID | ID - generated w/ the service provider's & delegator's addresses | | `claimableAmount` | BigInt | The amount delegated minus the pending decrease delegation | | `amount` | BigInt | The amount delegated | | `fromUser` | User | Reference to the user sending/delegating tokens | | `toUser` | User | Reference to the user receiving delegation | *** ### DeregisterProviderServicerEvent Description: | Field | Type | Description | | --------------- | ----------- | ----------- | | `id` | ID | | | `type` | ServiceType | | | `spId` | BigInt | | | `node` | ServiceNode | | | `owner` | User | | | `endpoint` | String | | | `unstakeAmount` | BigInt | | | `blockNumber` | BigInt | | *** ### GuardianTransactionExecutedEvent Description: | Field | Type | Description | | ----------------------- | ------ | ----------- | | `id` | ID | | | `targetContractAddress` | Bytes | | | `callValue` | BigInt | | | `functionSignature` | String | | | `callData` | Bytes | | | `returnData` | Bytes | | | `blockNumber` | BigInt | | *** ### IncreasedDelegatedStakeEvent Description: | Field | Type | Description | | ----------------- | ------ | ----------- | | `id` | ID | | | `delegator` | User | | | `serviceProvider` | User | | | `increaseAmount` | BigInt | | | `blockNumber` | BigInt | | *** ### IncreasedStakeEvent Description: | Field | Type | Description | | ---------------- | ------ | ----------- | | `id` | ID | | | `owner` | User | | | `newStakeAmount` | BigInt | | | `increaseAmount` | BigInt | | | `blockNumber` | BigInt | | *** ### Proposal Description: | Field | Type | Description | | --------------------------- | ------------- | ------------------------------------------------------------------------------ | | `id` | ID | Proposal ID from the event (auto-incrementing) | | `name` | String | Proposal name | | `description` | String | Proposal description | | `proposer` | User | Reference to the user submitting the proposal | | `submissionBlockNumber` | BigInt | | | `targetContractRegistryKey` | Bytes | | | `targetContractAddress` | Bytes | | | `callValue` | BigInt | | | `functionSignature` | String | | | `callData` | Bytes | | | `outcome` | Outcome | TODO: convert int to enum - Outcome | | `voteMagnitudeYes` | BigInt | Total vote weight for 'Yes' | | `voteMagnitudeNo` | BigInt | Total vote weight for 'No' | | `numVotes` | BigInt | Number of votes | | `votes` | [Vote](#vote) | Derived from `field: "proposal"` - Reference to the votes - user & vote weight | *** ### ProposalOutcomeEvaluatedEvent Description: | Field | Type | Description | | ----------------- | -------- | ----------- | | `id` | ID | | | `proposal` | Proposal | | | `outcome` | Outcome | | | `voteMagnitueYes` | BigInt | | | `voteMagnitudeNo` | BigInt | | | `numVotes` | BigInt | | | `blockNumber` | BigInt | | *** ### ProposalSubmittedEvent Description: | Field | Type | Description | | ------------- | -------- | ----------- | | `id` | ID | | | `proposal` | Proposal | | | `proposer` | User | | | `name` | String | | | `description` | String | | *** ### ProposalTransactionExecutedEvent Description: | Field | Type | Description | | ------------- | -------- | ----------- | | `id` | ID | | | `proposal` | Proposal | | | `success` | Boolean | | | `returnData` | Bytes | | | `blockNumber` | BigInt | | *** ### ProposalVoteSubmittedEvent Description: | Field | Type | Description | | ------------- | -------- | ----------- | | `id` | ID | | | `proposal` | Proposal | | | `voter` | User | | | `vote` | Vote | | | `currentVote` | VoteType | | | `voterStake` | BigInt | | | `blockNumber` | BigInt | | *** ### ProposalVoteUpdatedEvent Description: | Field | Type | Description | | -------------- | -------- | ----------- | | `id` | ID | | | `proposal` | Proposal | | | `voter` | User | | | `vote` | Vote | | | `voterStake` | BigInt | | | `currentVote` | VoteType | | | `previousVote` | VoteType | | | `blockNumber` | BigInt | | *** ### ProposalVetoedEvent Description: | Field | Type | Description | | ------------- | -------- | ----------- | | `id` | ID | | | `proposal` | Proposal | | | `blockNumber` | BigInt | | *** ### RegisterProviderServicerEvent Description: | Field | Type | Description | | ------------- | ----------- | ----------- | | `id` | ID | | | `type` | ServiceType | | | `spId` | BigInt | | | `node` | ServiceNode | | | `owner` | User | | | `endpoint` | String | | | `stakeAmount` | BigInt | | | `blockNumber` | BigInt | | *** ### RemoveDelegatorEvent Description: | Field | Type | Description | | -------------------- | ------------ | ----------- | | `id` | ID | | | `status` | LockupStatus | | | `owner` | User | | | `expiryBlock` | BigInt | | | `createdBlockNumber` | BigInt | | | `endedBlockNumber` | BigInt | | | `updatedCut` | BigInt | | | `delegator` | User | | *** ### ServiceNode Description: | Field | Type | Description | | --------------------- | ----------- | --------------------------------------------------------------------------- | | `id` | ID | ID - generated from service-type and spID | | `spId` | BigInt | Service provider ID - autoincrementing id created for each new service node | | `owner` | User | Reference to user that registered this service | | `type` | ServiceType | Reference to the service type | | `endpoint` | String | URI to access the service node | | `delegateOwnerWallet` | Bytes | Address used to confirm the ownership of the service node | | `createdAt` | Int | When the service node was created | | `isRegistered` | Boolean | Boolean if th service is registered/deregistered | *** ### ServiceType Description: | Field | Type | Description | | ---------- | ----------------------------------------- | ---------------------------------------- | | `id` | ID | The type of the service ie. creator-node | | `isValid` | Boolean | If the service is removed of not | | `minStake` | BigInt | Minimum Token Stake to run the service | | `maxStake` | BigInt | Max Token Stake to run the service | | `versions` | [ServiceTypeVersion](#servicetypeversion) | Derived from `field: "serviceType"` | *** ### ServiceTypeVersion Description: | Field | Type | Description | | ---------------- | ----------- | ----------- | | `id` | ID | | | `serviceType` | ServiceType | | | `serviceVersion` | String | | | `blockNumber` | BigInt | | *** ### SlashEvent Description: | Field | Type | Description | | ------------- | ------ | ----------- | | `id` | ID | | | `target` | User | | | `amount` | BigInt | | | `newTotal` | BigInt | | | `blockNumber` | BigInt | | *** ### UndelegateStakeEvent Description: | Field | Type | Description | | -------------------- | ------------ | ----------- | | `id` | ID | | | `status` | LockupStatus | | | `owner` | User | | | `expiryBlock` | BigInt | | | `createdBlockNumber` | BigInt | | | `endedBlockNumber` | BigInt | | | `serviceProvider` | User | | | `amount` | BigInt | | *** ### UpdateDeployerCutEvent Description: implements LockupEvent | Field | Type | Description | | -------------------- | ------------ | ----------- | | `id` | ID | | | `status` | LockupStatus | | | `owner` | User | | | `expiryBlock` | BigInt | | | `createdBlockNumber` | BigInt | | | `endedBlockNumber` | BigInt | | | `updatedCut` | BigInt | | *** ### User Description: | Field | Type | Description | | ----------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------ | | `id` | ID | Eth address of User | | `balance` | BigInt | Token balance | | `totalClaimableAmount` | BigInt | The total staked/delegated minus pending decrease stake/delegation | | `claimableStakeAmount` | BigInt | The total staked minus pending decrease stake | | `claimableDelegationReceivedAmount` | BigInt | The total delegation received from other users minus their pending decrease delegation | | `claimableDelegationSentAmount` | BigInt | The total delegation sent to other users minus my own pending decrease delegation | | `stakeAmount` | BigInt | The total staked | | `delegationReceivedAmount` | BigInt | The total delegated | | `delegationSentAmount` | BigInt | The total delegation sent | | `hasStakeOrDelegation` | Boolean | Boolean set to true if the user has stake or delegation | | `validBounds` | Boolean | If the user's stake is between the min/max stake | | `deployerCut` | BigInt | The percentage of the claim from the delegator that the deployer takes | | `services` | [ServiceNode](#servicenode) | Derived from `field: "owner"` - List of services operated by the user | | `minAccountStake` | BigInt | Max stake of the user as determined by number of services and service types | | `maxAccountStake` | BigInt | Min stake of the user as determined by number of services and service types | | `delegateTo` | [Delegate](#delegate) | Derived from `field: "fromUser"` - Reference to delegations (user & amount) sent by user | | `delegateFrom` | [Delegate](#delegate) | Derived from `field: "toUser"` - Reference to delegations (user & amount) received by user | | `pendingDecreaseStake` | DecreaseStakeEvent | Reference to request to pending decrease stake | | `pendingRemoveDelegator` | RemoveDelegatorEvent | DEPRECATED: Use event with service operator and delegator id | | `pendingUpdateDeployerCut` | UpdateDeployerCutEvent | Reference to request to update deployer cut | | `pendingUndelegateStake` | UndelegateStakeEvent | Reference to request to update undelegate stake | | `votes` | [Vote](#vote) | Derived from `field: "voter"` - Reference to votes by the user | | `createdAt` | BigInt | | *** ### Vote Description: | Field | Type | Description | | -------------------- | -------- | ------------------------------------------------ | | `id` | ID | ID - generated from proposal id and user address | | `proposal` | Proposal | Reference to the proposal | | `vote` | VoteType | TODO: update to enum - the voter's vote | | `magnitude` | BigInt | The vote weight - the voter's claimable stake | | `voter` | User | Reference the the user submitting the voter | | `createdBlockNumber` | BigInt | The block number the vote was created | | `updatedBlockNumber` | BigInt | The block number the vote was updated | *** import ApiReference from '../../components/ApiReference.jsx'