
Imagine uploading a raw audio recording and getting back a fully remixed version in a completely different genre - Pop to Jazz, Rock to Lo-fi - all powered by AI. That's exactly what we built as a proof of concept using Suno's AI, Nuxt 4, and Firebase.
In this article, we'll walk you through the architecture, key technical decisions, and code behind our Suno Remix PoC. Whether you're a music tech enthusiast or a developer exploring AI-powered audio tools, this guide will help you build something similar.
Suno is one of the most impressive AI music generation platforms available today. It can generate full songs from text prompts, create covers in different styles, extend existing tracks, and even separate stems (vocals vs. instruments).
The catch? Suno doesn't have an official public API. There's no suno.com/developers portal or API key system. Instead, a growing ecosystem of third-party services wraps Suno's capabilities behind a REST API (Application Programming Interface).
For this PoC, we chose sunoapi.org - the most documented third-party provider with support for the upload-cover endpoint, which is what enables the "remix" functionality. It costs approximately $0.005 per credit, which is very affordable for experimentation.
Here's the core challenge: Suno's API doesn't accept file uploads directly. Instead, it requires a public URL pointing to the audio file. This means we need an intermediary storage layer.
Our solution uses Firebase Storage as the bridge:
POST /api/remix → our Nitro server route forwards the request to sunoapi.org with the API keyGET /api/remix/:taskId → our server route checks sunoapi.org for the remix status every 30 secondsWhy this architecture?
| Decision | Reason |
|---|---|
| Firebase Storage | Gives us a publicly accessible URL for the uploaded WAV file |
| Nitro server routes | Keeps the Suno API key on the server - never exposed to the browser |
| Polling | Suno processes remixes asynchronously (1-3 min); we poll every 30 seconds |
| WaveSurfer.js | Industry-standard waveform visualization for comparing original vs. remixed audio |
| Layer | Technology |
|---|---|
| Frontend | Nuxt 4 + Vue 3 + Nuxt UI |
| Styling | Tailwind CSS 4 |
| Audio Visualization | WaveSurfer.js 7 |
| Server | Nitro (built into Nuxt) |
| Storage | Firebase Storage |
| Hosting | Firebase Hosting |
| API | sunoapi.org (third-party Suno proxy) |
The most interesting part of this project is how we bridge the gap between a user's local WAV file and Suno's requirement for a public URL.
We created a useFirebaseStorage composable that handles the upload with progress tracking:
import { getStorage, ref as storageRef, uploadBytesResumable, getDownloadURL } from 'firebase/storage'
async function uploadWavFile(file: File): Promise<string> {
const storage = getStorage(app)
const fileName = `${crypto.randomUUID()}.wav`
const fileRef = storageRef(storage, `uploads/${fileName}`)
const uploadTask = uploadBytesResumable(fileRef, file, {
contentType: 'audio/wav',
})
return new Promise((resolve, reject) => {
uploadTask.on('state_changed',
(snapshot) => {
// Track progress (0-100%)
uploadProgress.value = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
)
},
reject,
async () => {
// Get the public download URL
const downloadUrl = await getDownloadURL(uploadTask.snapshot.ref)
resolve(downloadUrl)
}
)
})
}
The getDownloadURL() call returns a publicly accessible Firebase Storage URL - exactly what Suno needs.
We never call Suno directly from the browser. Instead, we use a Nitro server route that reads the API key from server-only runtime configuration:
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const config = useRuntimeConfig()
const result = await fetch('https://api.sunoapi.org/api/v1/generate/upload-cover', {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.sunoApiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
uploadUrl: body.uploadUrl,
customMode: true,
style: body.style,
title: body.title,
model: 'V4_5',
}),
})
const data = await result.json()
return { taskId: data.data.taskId }
})
NUXT_SUNO_API_KEY environment variable is mapped to runtimeConfig.sunoApiKey (without public), which means Nuxt never bundles it into client-side JavaScript. This is the correct way to handle API secrets in Nuxt.Suno processes remixes asynchronously. We poll every 30 seconds:
pollInterval = setInterval(async () => {
const result = await $fetch(`/api/remix/${taskId.value}`)
if (result.status === 'SUCCESS') {
tracks.value = result.tracks
status.value = 'completed'
stopPolling()
}
}, 30000)
When the remix is ready, the response includes an audio_url pointing to the generated track on Suno's CDN (Content Delivery Network).
The UI is intentionally simple - this is a PoC, after all. The entire flow happens on a single page:
Here's what the upload screen looks like:

Once you hit Remix, the app uploads the file to Firebase Storage and kicks off the AI processing. The 3-step progress indicator keeps you informed:

When the remix is complete, you get a side-by-side comparison with waveform visualization - the original on the left, and the AI-generated remix(es) alongside it:

We wrapped WaveSurfer.js in a Vue component with <ClientOnly> to avoid SSR (Server-Side Rendering) issues:
<ClientOnly>
<WaveformPlayer
:audio-url="track.audioUrl"
label="Remix - Jazz"
/>
</ClientOnly>
The player provides play/pause controls, a progress bar, and time display - all styled to match the dark theme.
Firebase Hosting serves the Nuxt static output. The setup is straightforward:
{
"hosting": {
"public": ".output/public",
"cleanUrls": true,
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
},
"storage": {
"rules": "firebase-storage.rules"
}
}
Firebase Storage rules restrict uploads to WAV and MP3 files under 50 MB:
match /uploads/{fileName} {
allow write: if request.resource.size < 50 * 1024 * 1024
&& (request.resource.contentType == 'audio/wav'
|| request.resource.contentType == 'audio/mpeg');
allow read: if true;
}
Deploy with:
pnpm build
firebase deploy
Even for a PoC, security matters:
| Concern | Solution |
|---|---|
| API key exposure | Stored in runtimeConfig (server-only), never in runtimeConfig.public |
| File abuse | Firebase Storage rules: WAV/MP3 only, 50 MB max |
| Input validation | Server routes validate URL format, string lengths |
| Secrets in git | .env in .gitignore; only .env.example committed |
One thing that caught us off guard during testing - Suno has built-in copyright detection. When we tried remixing a well-known track (AC/DC's "Back in Black"), the API returned an error:
Error code: 413
Error message: Uploaded audio matches existing work of art.
Suno's system fingerprints uploaded audio and compares it against known copyrighted works. If it detects a match, the remix is rejected with a GENERATE_AUDIO_FAILED status.
This is actually a responsible feature - it prevents the platform from being used to create unauthorized covers or derivatives of copyrighted music. For our PoC, we switched to an original recording and everything worked perfectly.
This PoC demonstrates the core flow, but there are plenty of ways to extend it:
You'll need your own Suno API key from sunoapi.org and a Firebase project if you want to build something similar.
Have questions or want to share your remix results? Reach out to us at musictechlab.io.
Have a similar project in mind? We'd love to hear about it.
Get in touch to discuss how we can help bring your vision to life.
Building a Custom Music Delivery Platform on the Revelator API
A practical guide for music distributors evaluating a hybrid approach: custom frontend with Revelator's API as the delivery backbone. Covers architecture, DDEX integration, territory handling, and when to go fully independent.
Data Modeling in MongoDB Using Design Patterns
The most useful data modeling design patterns in MongoDB are attributed, three, and extended reference patterns.