Overview
The Royalti.io Release Management system streamlines the process of preparing and distributing music releases. This guide covers creating releases, managing tracks and media, submitting for review, and leveraging the auto-creation pipeline that generates Products and Assets automatically upon approval.
Prerequisites
Addon Activation
The Releases feature requires an active subscription. Contact your account administrator to enable this addon for your workspace.
Required Permissions
- Users: Can create and manage their own releases
 
- Admins: Can review, approve/reject releases, and manage all releases in the workspace
 
Authentication
All release endpoints require authentication with a Bearer token.
 
const token = 'your_jwt_token';
const headers = {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json'
};
 
Release Creation Workflow
Create Draft Release
Create a new release with required metadata and at least one track.const response = await fetch('https://api.royalti.io/releases', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Summer Vibes',
    displayArtist: 'The Wave Collective',
    artists: {
      'Artist Name': 'primary'  // Auto-creates artist if name provided
      // OR use existing artist UUID: '550e8400-e29b-41d4-a716-446655440000': 'primary'
    },
    format: 'Single',           // Single, EP, Album, Compilation
    type: 'Audio',              // Audio, Video, Mixed
    version: 'Radio Edit',
    label: 'Independent Records',
    copyright: '2024 The Wave Collective',
    releaseDate: '2024-12-01',
    preReleaseDate: '2024-11-15',
    mainGenre: ['Pop', 'Electronic'],
    subGenre: ['Synth Pop'],
    explicit: null,             // 'explicit', 'clean', or null
    contributors: [
      {
        role: 'Producer',
        name: 'John Mix',
        isni: '0000000123456789'
      },
      {
        role: 'Songwriter',
        name: 'Jane Melody',
        isni: null
      }
    ],
    metadata: {
      upc: '123456789012',
      catalogNumber: 'IND001'
    },
    tracks: [
      {
        title: 'Summer Nights',
        displayArtist: 'The Wave Collective',
        artists: {
          'The Wave Collective': 'primary',
          'Guest Vocalist': 'featuring'
        },
        version: 'Radio Edit',
        isrc: 'USCM51500001',
        iswc: 'T-123.456.789-0',
        duration: 210,
        language: 'en',
        mainGenre: ['Pop'],
        subGenre: ['Synth Pop'],
        explicit: 'clean',
        lyrics: 'Verse 1\nChorus\nVerse 2\nChorus\nBridge\nChorus',
        contributors: {
          songwriter: ['Jane Melody', 'The Wave Collective'],
          producer: ['John Mix']
        },
        metadata: {
          bpm: 120,
          key: 'C Major'
        }
      }
    ]
  })
});
const result = await response.json();
console.log('Release created:', result.data.id);
 Upload Media Files
Add artwork, audio files, and supporting documents to your release.// Upload release-level media (artwork, documents)
const formData = new FormData();
formData.append('files', artworkBuffer, 'album-cover.jpg');
formData.append('files', contractBuffer, 'distribution-agreement.pdf');
const mediaResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/media/files`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`
  },
  body: formData
});
// Upload track audio file
const trackFormData = new FormData();
trackFormData.append('file', audioBuffer, 'summer-nights.wav');
const trackMediaResponse = await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/${trackId}/media/file`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`
    },
    body: trackFormData
  }
);
Media files are stored in temporary storage until the release is approved. Upon approval, they are automatically transferred to permanent storage.
 Submit External Links
For large files, submit links from WeTransfer, Google Drive, Dropbox, or other cloud services.const linkResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/media/links`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    links: [
      {
        url: 'https://wetransfer.com/downloads/abc123',
        name: 'High-Quality Masters',
        type: 'audio'
      },
      {
        url: 'https://drive.google.com/file/d/xyz789',
        name: 'Album Artwork 4K',
        type: 'image'
      }
    ]
  })
});
// For individual track media links
const trackLinkResponse = await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/${trackId}/media/link`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: 'https://www.dropbox.com/s/track123',
      name: 'Summer Nights Master',
      type: 'audio'
    })
  }
);
Supported platforms: WeTransfer, Google Drive, Dropbox, OneDrive, Box, Mega, MediaFire
 Submit for Review
Once all metadata and media are complete, submit the release for admin review.const submitResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/submit`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
const submitResult = await submitResponse.json();
if (submitResult.success) {
  console.log('Release submitted successfully');
  console.log('Status:', submitResult.data.status); // 'submitted'
}
Releases must have at least 1 track and 1 primary artist to be submitted. Only releases in ‘draft’ or ‘rejected’ status can be submitted.
  
Required Fields
- title: Release title
 
- displayArtist: How artist name appears on the release
 
- artists: Object mapping artist UUIDs or names to roles (‘primary’, ‘featuring’)
 
- tracks: Array of at least one track
 
Optional Fields
- format: Single (default), EP, Album, Compilation, Soundtrack, Live, Remix, Remaster
 
- type: Audio (default), Video, Mixed
 
- version: Deluxe Edition, Remastered, etc.
 
- label: Record label name
 
- copyright: Copyright notice
 
- releaseDate: Public release date
 
- preReleaseDate: Early access date
 
- mainGenre: Array of primary genres
 
- subGenre: Array of secondary genres
 
- explicit: ‘explicit’, ‘clean’, or null
 
- contributors: Array of contributors with roles and ISNI codes
 
- metadata: Custom fields (UPC, catalog number, etc.)
 
Artist Resolution
The system supports flexible artist handling:
// Option 1: Use existing artist UUIDs
{
  artists: {
    '550e8400-e29b-41d4-a716-446655440000': 'primary',
    '660e8400-e29b-41d4-a716-446655441111': 'featuring'
  }
}
// Option 2: Use artist names (auto-creates if not found)
{
  artists: {
    'John Doe': 'primary',
    'Jane Smith': 'featuring'
  }
}
// Artist roles
// - primary: Main artist(s) on the release
// - featuring: Featured/guest artist(s)
 
Track Management
Creating Tracks
Tracks can be added during release creation or afterward:
const trackResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/tracks`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Midnight Dreams',
    displayArtist: 'The Wave Collective',
    artists: {
      'The Wave Collective': 'primary'
    },
    version: 'Extended Mix',
    isrc: 'USCM51500002',
    duration: 285,
    language: 'en',
    mainGenre: ['Electronic'],
    explicit: null
  })
});
 
Linking Existing Assets
Link previously created Assets to a release with optional overrides:
const linkResponse = await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/link-asset`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      assetId: '770e8400-e29b-41d4-a716-446655442222',
      trackNumber: 2,  // Optional specific position
      overrides: {
        title: 'Midnight Dreams (Radio Edit)',
        version: 'Radio Edit',
        explicit: 'clean',
        duration: 210  // Override for radio edit
      }
    })
  }
);
 
Linking Assets preserves the original Asset data while allowing release-specific customizations through overrides.
 
Reordering Tracks
const reorderResponse = await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/reorder`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      trackOrder: [
        'track-3-uuid',
        'track-1-uuid',
        'track-2-uuid'
      ]
    })
  }
);
 
Updating Tracks
const updateResponse = await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/${trackId}`,
  {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      title: 'Summer Nights (Updated Mix)',
      duration: 215,
      metadata: {
        bpm: 122,
        key: 'D Major'
      }
    })
  }
);
 
Storage Architecture
All media is initially stored in a temporary bucket. Upon release approval, media is automatically transferred to permanent storage based on tenant settings.
 
Release-level media supports multiple files (artwork, documents, promotional materials):
const formData = new FormData();
formData.append('files', coverArtBuffer, 'cover.jpg');
formData.append('files', backCoverBuffer, 'back-cover.jpg');
formData.append('files', contractBuffer, 'contract.pdf');
const response = await fetch(`https://api.royalti.io/releases/${releaseId}/media/files`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`
  },
  body: formData
});
const result = await response.json();
result.data.forEach(media => {
  console.log(`Uploaded: ${media.name} (${media.type})`);
  console.log(`Temp URL: ${media.cloudUrl}`);
  console.log(`Size: ${media.metadata.fileSize} bytes`);
});
 
Each track supports one audio or video file:
const formData = new FormData();
formData.append('file', audioBuffer, 'track.wav');
const response = await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/${trackId}/media/file`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`
    },
    body: formData
  }
);
 
Each track can have only one audio or video file. Uploading a new file replaces the existing one.
 
// Delete release media
await fetch(`https://api.royalti.io/releases/${releaseId}/media/${mediaId}`, {
  method: 'DELETE',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
// Delete track media
await fetch(
  `https://api.royalti.io/releases/${releaseId}/tracks/${trackId}/media/${mediaId}`,
  {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);
 
Review and Approval Process
Feedback System
Users and admins can provide feedback on releases:
const feedbackResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/feedback`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: 'The track mix sounds great! Consider adding lyrics metadata.',
    isInternal: false  // Visible to release owner
  })
});
// Admin internal feedback (visible only to other admins)
const internalFeedback = await fetch(`https://api.royalti.io/releases/${releaseId}/feedback`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: 'ISRC validation required before final approval.',
    isInternal: true  // Only visible to admins
  })
});
 
Admin Review (Admin Only)
Admins can approve or reject submitted releases:
// Approve release
const approveResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/review`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    action: 'approve',
    feedback: 'All metadata verified. Approved for distribution.'
  })
});
// Reject release with feedback
const rejectResponse = await fetch(`https://api.royalti.io/releases/${releaseId}/review`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    action: 'reject',
    feedback: 'Missing ISRC codes for tracks 2 and 3. Please update and resubmit.'
  })
});
 
Only releases with status ‘submitted’ can be reviewed. Approved releases automatically trigger the auto-creation pipeline.
 
Auto-Creation Pipeline
When a release is approved, an automated pipeline generates Product and Asset records:
Pipeline Stages
Approved → Auto-creation: pending → Product/Asset Generation →
Media Transfer → Auto-creation: success → Status: completed
 
Monitoring Auto-Creation
const releaseResponse = await fetch(`https://api.royalti.io/releases/${releaseId}`, {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
const release = await releaseResponse.json();
console.log('Status:', release.data.status);                    // 'approved' or 'completed'
console.log('Auto-creation:', release.data.autoCreationStatus); // 'pending', 'success', 'failed'
if (release.data.autoCreationStatus === 'success') {
  console.log('Product created:', release.data.productId);
  console.log('Assets created:', release.data.assetIds);
}
 
Auto-Creation Status Values
- null: Auto-creation not started (draft, submitted, under_review, rejected)
 
- pending: Pipeline in progress
 
- success: Product and Assets successfully created
 
- failed: Pipeline encountered errors
 
Error Handling
If auto-creation fails, admins can investigate and revert:
// Revert failed release for re-review (Admin only)
const revertResponse = await fetch(
  `https://api.royalti.io/releases/${releaseId}/revert-status`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      targetStatus: 'submitted',
      reason: 'Auto-creation failed due to missing artist metadata. Reverting for corrections.'
    })
  }
);
 
Status reversion creates a complete audit trail and clears auto-creation artifacts.
 
Querying and Filtering Releases
List All Releases
const response = await fetch('https://api.royalti.io/releases?page=1&limit=20', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
const result = await response.json();
console.log('Total releases:', result.pagination.total);
console.log('Releases:', result.data.releases);
console.log('Statistics:', result.stats);
 
Filter by Status
const draftReleases = await fetch(
  'https://api.royalti.io/releases?status=draft&limit=50',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);
// Available statuses: draft, submitted, under_review, approved, rejected, completed
 
const albums = await fetch(
  'https://api.royalti.io/releases?format=Album&type=Audio',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);
 
Search Releases
const searchResults = await fetch(
  'https://api.royalti.io/releases?search=Summer',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);
// Searches title, displayArtist, and description fields
 
Release Statistics
const statsResponse = await fetch('https://api.royalti.io/releases/stats', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
const stats = await statsResponse.json();
console.log('Draft:', stats.data.draft);
console.log('Submitted:', stats.data.submitted);
console.log('Approved:', stats.data.approved);
console.log('Completed:', stats.data.completed);
console.log('Rejected:', stats.data.rejected);
 
Best Practices
Before creating a release, prepare:
Audio Files:
- Format: WAV, FLAC, or high-quality MP3
 
- Sample rate: 44.1 kHz or 48 kHz
 
- Bit depth: 16-bit or 24-bit
 
- Bitrate: 320 kbps (for MP3)
 
Artwork:
- Format: JPG or PNG
 
- Resolution: Minimum 3000x3000px
 
- Color mode: RGB
 
- File size: Under 10MB
 
Video Files:
- Format: MP4, MOV, or AVI
 
- Resolution: 1080p or 4K
 
- Codec: H.264 or H.265
 
Error Handling Pattern
async function createRelease(releaseData) {
  try {
    const response = await fetch('https://api.royalti.io/releases', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(releaseData)
    });
    const result = await response.json();
    if (!response.ok) {
      throw new Error(result.error?.message || 'Failed to create release');
    }
    return result.data;
  } catch (error) {
    console.error('Release creation failed:', error.message);
    throw error;
  }
}
 
Validation Before Submission
async function validateReleaseBeforeSubmit(releaseId) {
  const response = await fetch(`https://api.royalti.io/releases/${releaseId}`, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  const release = await response.json();
  const errors = [];
  // Validate tracks
  if (!release.data.tracks || release.data.tracks.length === 0) {
    errors.push('Release must have at least one track');
  }
  // Validate primary artist
  const hasPrimaryArtist = release.data.artists.some(a => a.type === 'primary');
  if (!hasPrimaryArtist) {
    errors.push('Release must have at least one primary artist');
  }
  // Validate media
  const tracksWithoutMedia = release.data.tracks.filter(
    t => !t.media || t.media.length === 0
  );
  if (tracksWithoutMedia.length > 0) {
    errors.push(`${tracksWithoutMedia.length} track(s) missing audio files`);
  }
  if (errors.length > 0) {
    throw new Error(`Validation failed:\n${errors.join('\n')}`);
  }
  return true;
}
// Usage
try {
  await validateReleaseBeforeSubmit(releaseId);
  await submitRelease(releaseId);
} catch (error) {
  console.error(error.message);
}
 
Integration with DDEX
Releases integrate seamlessly with DDEX distribution:
// After release is completed, generate DDEX message
const ddexResponse = await fetch('https://api.royalti.io/ddex/generate-ern', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    productId: release.data.productId,  // From completed release
    providers: ['spotify', 'apple_music'],
    messageType: 'ERN'
  })
});
 
Status Workflow
Release Status Transitions
draft → submitted → under_review → approved → completed
                                      ↓
                                  rejected → draft (resubmit)
 
Editable Statuses
Only releases in ‘draft’ or ‘rejected’ status can be edited:
- Update metadata
 
- Add/remove/reorder tracks
 
- Upload/delete media
 
- Update track information
 
Admin-Only Operations
- Review releases (approve/reject)
 
- Revert release status
 
- View internal feedback
 
Core Release Operations
Workflow Operations
Track Management