GET /v1/songs/uploads/:id
An upload's status, its transcript and the tracks made from it
Fetch one song upload. Poll this after a Reimagine upload until status is transcribed, then read the lyrics we heard and the words we weren't sure of. It also lists every track made from the upload.
An upload stays readable after its 24 hours are up. By then deleted is true and audioUrl, transcript and lyrics are null, but generations still lists what you made from it.
Request
GET https://pub.finetuning.ai/v1/songs/uploads/:idHeaders
| Header | Type | Required | Description |
|---|---|---|---|
X-API-Key | string | Yes | Your API key |
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The upload ID from POST /v1/songs/uploads |
Example request
curl https://pub.finetuning.ai/v1/songs/uploads/22970e5b-c403-494c-b10d-846df6dde7c5 \
-H "X-API-Key: ft_live_your_key_here"async function waitForTranscript(uploadId: string) {
for (;;) {
const res = await fetch(`https://pub.finetuning.ai/v1/songs/uploads/${uploadId}`, {
headers: { 'X-API-Key': process.env.FINETUNING_API_KEY! },
})
const { data: upload } = await res.json()
if (upload.status !== 'transcribing') return upload
await new Promise((r) => setTimeout(r, 5000))
}
}Response
{
"data": {
"id": "22970e5b-c403-494c-b10d-846df6dde7c5",
"feature": "reimagine",
"name": "my-song",
"format": "mp3",
"status": "transcribed",
"durationSeconds": 180,
"originalDurationSeconds": 212.4,
"window": { "startSeconds": 30, "lengthSeconds": 180 },
"lyricsCap": 3240,
"lyrics": "[verse]\nLate night on the empty street\nNeon humming to the beat\n\n[chorus]\nHold on, hold on\nWe were never gone",
"transcript": {
"language": "en",
"sections": [
{
"label": "Verse",
"lines": [
{ "words": [{ "w": "Late", "low": false }, { "w": "night", "low": false }, { "w": "on", "low": false }, { "w": "the", "low": false }, { "w": "empty", "low": false }, { "w": "street", "low": false }] },
{ "words": [{ "w": "Neon", "low": true }, { "w": "humming", "low": false }, { "w": "to", "low": false }, { "w": "the", "low": false }, { "w": "beat", "low": false }] }
]
},
{
"label": "Chorus",
"lines": [
{ "words": [{ "w": "Hold", "low": false }, { "w": "on,", "low": false }, { "w": "hold", "low": false }, { "w": "on", "low": false }] },
{ "words": [{ "w": "We", "low": false }, { "w": "were", "low": false }, { "w": "never", "low": false }, { "w": "gone", "low": false }] }
]
}
],
"lowConfidenceCount": 1,
"wordCount": 19,
"noVocals": false,
"confidenceSource": "heuristic"
},
"lowConfidenceWords": [
{ "section": 0, "line": 1, "word": 0, "text": "Neon" }
],
"fingerprintStatus": "skipped",
"errorMessage": null,
"pricing": { "transcriptionPaid": true, "transcriptionCreditApplied": true, "nextReimagineCredits": 2, "retranscribeCredits": 1 },
"audioUrl": "https://dev.finetuning.ai/media/uploads/22970e5b-…?exp=1790866400&sig=…",
"createdAt": "2026-09-30T13:53:20.151Z",
"expiresAt": "2026-10-01T13:53:20.151Z",
"deleted": false,
"generations": [
{
"id": "959ef62f-45c1-4cb6-a4db-fa1da227f5fe",
"type": "reimagine",
"status": "completed",
"title": "my-song (Custom)",
"style": "Custom",
"audioUrl": "https://media.finetuning.ai/audio/965f8c8d-….mp3",
"errorMessage": null,
"credits": 1,
"usedTranscriptionCredit": true,
"refunded": false,
"createdAt": "2026-09-30 13:53:44"
}
]
}
}The upload object
| Field | Type | Description |
|---|---|---|
id | string | Upload id — pass it as uploadId |
feature | string | reimagine or remove_vocals — what it was uploaded for |
name | string | From the file name, without the extension |
format | string | mp3 or wav |
status | string | See below |
durationSeconds | number | Length of what we kept (at most 180) |
originalDurationSeconds | number | Length of the file you sent |
window | object | startSeconds and lengthSeconds of the part we kept |
lyricsCap | number | Most lyric characters Reimagine accepts for this upload |
lyrics | string | null | The transcript as lyrics with section tags — what Reimagine sings when you don't send lyrics. After a Reimagine it holds the lyrics that were last used |
transcript | object | null | Sections → lines → words, each word with low: true when we weren't sure of it. noVocals: true means we heard no singing |
lowConfidenceWords | array | Every uncertain word with its position (section, line, word are zero-based indexes into transcript.sections) |
fingerprintStatus | string | skipped, clear, match or error — the catalogue check, where enabled |
errorMessage | string | null | Why the last step failed, when it did |
pricing | object | What the next steps cost. nextReimagineCredits: 1 while this upload's paid transcription credit is unused, else 2. retranscribeCredits: what /transcribe would cost now (0 for the free retry). transcriptionPaid / transcriptionCreditApplied: whether a transcription was paid for, and whether a Reimagine has used its credit |
audioUrl | string | null | A signed link to the audio we kept, valid until an hour after expiresAt. null once deleted |
createdAt / expiresAt | string | Uploaded at / deleted at (24 hours later) |
deleted | boolean | true once the audio and transcript have been deleted |
generations | array | Tracks made from this upload, newest first (up to 20). Only on this endpoint; the list and create responses return []. Each has credits (what that render charged) and usedTranscriptionCredit (true for the first render, which cost 1 on top of the transcription) |
Status
| Status | Meaning |
|---|---|
ready | Stored. A Remove vocals upload stays here; it doesn't need the lyrics |
transcribing | We're listening for the lyrics (usually 10–90 seconds) |
transcribed | Lyrics ready in lyrics and transcript |
no_vocals | We didn't hear any singing. Send your own lyrics to Reimagine it |
failed | We couldn't listen to it. Try again, or send your own lyrics |
blocked | Refused by the catalogue check and deleted |
How sure are the "low" flags? Our speech recogniser doesn't report per-word confidence yet, so today (confidenceSource: "heuristic") we flag the words that look wrong: garbled characters, letters from a script the rest of the song isn't in, and the stock phrases speech recognition invents over silence. When per-word confidence is available the same fields carry it (confidenceSource: "words" or "segments"). Treat the flags as "worth checking", not as a complete list.
Errors
| Code | Status | Description |
|---|---|---|
NOT_FOUND | 404 | No upload with that id on your account |