Lyrics timestamp alignment API
This is not speech recognition. The text always uses the lyrics you supply verbatim; we only place each character at the correct time — so you never get "misheard characters making the lyrics mismatch".
Endpoint description
It aligns existing lyrics with audio and outputs line-level + character-level timestamps, for karaoke character highlighting, scrolling lyrics and subtitle generation.
Endpoint: POST /api/music/lyrics-align
Authentication: see Authentication; the request header is Authorization: Bearer YOUR_API_KEY
Billing: charged per call (currently 0.1 ¥ per call by default); for a platform song with vocal separation selected, the platform's "vocal/backing separation" price is charged once more. The amount actually charged follows the current back-office price configuration.
Average time: 5–15 seconds (depending on audio length and whether vocal separation has to be done on the spot)
Why calibration is needed: generated timestamps are not always accurate
The timestamped lyrics API returns the generation side's per-word timestamps directly, and they carry inaccuracies:
- reprises, heavy harmonies, rap and long held notes often drift overall or shift individual characters;
- some songs are missing a stretch at the start (for example a pure intro, with the first segment only starting in the teens);
- some lyric lines are hard to pin to a clear time anchor.
Lyrics timestamp alignment re-does the time alignment on the lyrics you supply yourself: only the timing is corrected, never the text, which suits cases that care about "does it line up" (KTV character highlighting, per-character subtitles, sing-along scoring).
Recommended approach (three steps, best results)
- Extract the vocal first (vocal/backing separation) and align only the vocal track — backing and harmonies stop interfering. Measured basis: in a line-by-line comparison of "full mix vs vocal track" for the same song, 5 of 28 lines differed by more than 0.25 s (the largest by 2.64 s); two independent engines differed on average by 0.239 s on the vocal track and 0.508 s on the full mix — nearly half the agreement. So for character highlighting (KTV/subtitles) it is recommended to switch it on. (The exact gain is being re-measured with the new engine.)
- Provide the complete lyrics: the whole song, line by line, matching the audio one-to-one. A missing sentence or section shifts all the following lines; do not include timestamps such as
[00:23.82]in the lyrics (they are ignored automatically). - For a platform song you can simply tick
vocal_separate: true, and we do "separation + alignment" in one step.
Three ways to use it
| Usage | What you send | Where the lyrics come from | Vocal separation |
|---|---|---|---|
| External song · public URL | audio_url + lyrics |
You supply them | ❌ Not supported |
| External song · direct local upload (new in 2026-10) | audio_object_key + lyrics |
You supply them | ❌ Not supported |
| Platform song (generated on this site) | song_id |
We take them from the song record | ✅ Optional |
How the parameters relate (the easiest thing to get wrong — read carefully)
- Sending
song_id→ platform song route: do not sendlyrics/audio_url/audio_object_keyas well; - Sending
audio_urloraudio_object_key→ external song route: send only one of the two, and do not sendsong_id; vocal_separateonly works for platform songs; sending it for an external song is rejected (the audio is not on our side, so it cannot be separated).
New: direct local upload (the audio never passes through our servers)
Use this when the audio is on your machine and you would rather not upload it to your own public storage first. Two steps:
POST /api/music/lyrics-align/upload-ticketwithfile_name/file_size/content_typeand get back aput_url(a signed URL with no long-lived credentials) and anobject_key;PUTthe file toput_url, carrying thecontent_typefrom the response exactly as given (it takes part in the signature; one changed character means 403);- When submitting the alignment, send
audio_object_keyinstead ofaudio_url.
The credential is single-use: once the alignment finishes (success or failure) the server deletes the object, and reusing the same key reports an error.
New: the lyrics are now optional
External songs used to require lyrics. Now you can omit them — the server runs recognition once and uses the recognised text as the lyrics.
The trade-off must be stated clearly: the text then follows the recognition result, so individual characters may be wrong. Such responses are clearly marked with
lyrics_source: "asr", confidence: "low" and, in warnings[],
{"code":"no_reference_lyrics"}.
For character-level highlighting / KTV, always supply the original lyrics.
Request parameters
Headers
| Parameter | Type | Required | Notes |
|---|---|---|---|
| Authorization | string | yes | Bearer YOUR_API_KEY |
| Content-Type | string | yes | application/json |
Body parameters
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
| song_id | string | one of two | - | Platform song ID (platform song route) |
| lyrics | string | no | - | The original lyrics, lines separated by \n. Can now be omitted — see "the lyrics are now optional" above |
| audio_url | string | one of two for external | - | Audio address; must be publicly downloadable |
| audio_object_key | string | one of two for external | - | Key of the direct-upload object (call /lyrics-align/upload-ticket first) |
| vocal_separate | boolean | no | false |
Whether to separate vocals first (platform songs only) |
| language | string | no | auto-detected | Chinese / English etc.; when omitted it is guessed from the lyrics |
| granularity | string | no | both |
both / line (no per-character output) / word |
| sections | string | no | keep |
keep retains section tags such as [Chorus] (they do not take part in the timeline) / drop |
| format | string | no | json |
json / lrc / lrc-word (the latter two also return the lrc and lrc_word text) |
Limits
| Item | Limit |
|---|---|
| Audio length | ≤ 6 minutes (longer returns an error and a full refund) |
| Audio source | Must be publicly downloadable; addresses that need a login are not accepted |
| Lyrics format | Plain text, lines separated by \n; section tags such as [Verse] and 【主歌】 are kept as-is but do not take part in the timeline |
| Batching | One song per call; loop over multiple songs yourself |
| Result storage | This endpoint does not persist anything; save the returned result yourself |
Request examples
External song (lyrics + audio address)
curl -X POST https://www.suno-api.io/api/music/lyrics-align \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lyrics": "Wandering the chilly streets\nNight wind brushes my eyes\nTime flows on like water",
"audio_url": "https://your-bucket.example.com/song.mp3"
}'
Platform song (no separation)
curl -X POST https://www.suno-api.io/api/music/lyrics-align \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"song_id": "17e52af7-b5df-4877-a98a-dce94e0e6aa2"}'
Platform song + vocal separation (recommended for karaoke)
curl -X POST https://www.suno-api.io/api/music/lyrics-align \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"song_id": "17e52af7-b5df-4877-a98a-dce94e0e6aa2", "vocal_separate": true}'
Response
Success response
{
"success": true,
"lyrics_source": "platform",
"vocal_separate": false,
"coverage": 0.935,
"exact": 196,
"fuzzy": 4,
"total": 214,
"lines": [
{
"text": "Wandering the chilly streets",
"start": 21.66,
"end": 24.06,
"chars": [
{"char": "W", "start": 21.66, "end": 21.74, "word": true},
{"char": "a", "start": 21.98, "end": 22.22, "word": true}
]
}
]
}
Field reference
| Field | Type | Notes |
|---|---|---|
success |
boolean | Whether the request succeeded |
lyrics_source |
string | platform = lyrics taken from the platform song; user = lyrics supplied by you; asr = you supplied none and the text came from recognition |
vocal_separate |
boolean | Whether vocal separation was used this time |
confidence |
string | high / low (always low when lyrics_source=asr) |
warnings[] |
array | Degradation notices; contains {"code":"no_reference_lyrics"} when there were no lyrics |
coverage |
number | Alignment quality: share of characters with a clear time anchor (0–1) |
exact / fuzzy / total |
number | Exact-match characters / homophone-match characters / valid lyric characters |
lines[].text |
string | One lyric line, character for character equal to your original |
lines[].start / end |
number | Start and end time of the line (seconds) |
lines[].section |
boolean | Whether the line is a section tag (true means no time and no highlighting) |
lines[].chars[] |
array | Per-character times: char, start/end in seconds, word whether it counts towards the timeline (punctuation is false) |
words[] |
array | Flat per-character list: text / start / end / line; not returned when granularity=line |
lrc |
string | Line-level LRC text (only with format=lrc or lrc-word) |
lrc_word |
string | Enhanced per-character LRC with inline <mm:ss.xx> tags (only with format=lrc-word) |
How to use coverage (three tiers)
| coverage | Suggested use |
|---|---|
| ≥ 0.9 | Character highlighting (karaoke) is fine |
| 0.8 – 0.9 | Mix per-character and whole-line, or highlight whole lines only and treat per-character as indicative |
| < 0.8 | Whole-line highlighting only (reprises, heavy harmonies, dialects and rap tend to score low) |
Status and timing
| Scenario | Measured time |
|---|---|
| Platform song · no separation | about 4.5 s |
| Platform song · separation (vocal track already present) | about 11 s |
| Platform song · separation (must be separated on the spot) | about 25 s, up to about 1 minute |
| External song · lyrics + audio address | about 5–15 s (depending on audio length) |
The server's single-run limit is 8 minutes; set your client timeout to 120 seconds or more.
Error responses
{
"success": false,
"message": "音频超过 6 分钟上限(实际 7.2 分钟)"
}
| Scenario | Charged? |
|---|---|
| No audio at all (none of the three sent) | Not charged (processing never started) |
audio_url and audio_object_key sent together |
Not charged (processing never started) |
| The direct-upload object is not on disk yet / has been deleted | Not charged (processing never started) |
Invalid granularity / sections / format |
Not charged (processing never started) |
| The platform song does not exist / is not on the current account | Not charged (processing never started) |
| Missing or invalid parameters | Not charged (processing never started) |
| Audio longer than the 6-minute limit | Full refund |
| Audio not downloadable (not a public address / needs a login) | Full refund |
| No lyrics supplied and server-side recognition failed | Full refund |
| Vocal separation ticked but no usable vocal track | Full refund (including the separation fee) |
| Alignment service error or timeout | Full refund |
Principle: no final result means a full refund. The charge applies only when per-character timestamps are actually returned, and refunds are automatic — no request needed.
Billing
| Item | When it is charged | Notes |
|---|---|---|
| Lyrics timestamp alignment (base) | Every call | Charged per call (currently 0.1 ¥ per call by default); the amount actually charged follows the back-office price configuration |
| Transcription surcharge | Only when no lyrics are supplied | The server must run speech recognition first, a cost the "with lyrics" tier does not have, so it is priced separately |
| Vocal separation (optional, platform songs only) | When vocal_separate is ticked |
Charged once at the platform's existing "vocal/backing separation" price and settled together with this endpoint |
The combination:
amount due = base price + (no lyrics supplied ? transcription surcharge : 0) + (separation ticked ? separation price : 0)
The vocal separation fee is charged only once: if you have already separated the song and the vocal track is available directly, this endpoint charges it; if separation has to happen on the spot, the separation step charges it.
Full example (Python)
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://www.suno-api.io"
def align_lyrics(song_id=None, lyrics=None, audio_url=None, vocal_separate=False):
payload = {}
if song_id:
payload["song_id"] = song_id
payload["vocal_separate"] = vocal_separate
else:
payload["lyrics"] = lyrics
payload["audio_url"] = audio_url
r = requests.post(
f"{BASE}/api/music/lyrics-align",
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
timeout=600,
)
data = r.json()
if not data.get("success"):
raise RuntimeError(data.get("message"))
return data
res = align_lyrics(song_id="17e52af7-b5df-4877-a98a-dce94e0e6aa2", vocal_separate=True)
print("alignment quality coverage =", res["coverage"])
for line in res["lines"]:
print(f'{line["start"]:7.2f} {line["text"]}')
# per-character karaoke: every character in line["chars"] has its own start / end
FAQ
Q: Is the output per character or per line?
A: Both. lines[].start is line-level; lines[].chars[].start / end are per character — the player takes whichever it needs.
Q: Why is the returned text identical to what I sent? A: By design. We do not recognise-and-rewrite; we only align timing, so the lyrics can never be "corrected into typos".
Q: coverage is high but it still sounds slightly off?
A: Per-character alignment error is usually in the tens of milliseconds; if the whole song is off by a fixed amount, it is most likely a stretch of silence at the start of the audio, so trim it and try again.
Q: Why did separation work a moment ago, and now it reports "vocal track unavailable"? A: Separation results are short-lived temporary addresses (measured to expire after roughly half an hour). In that case the endpoint tries to separate again automatically; if it still fails you get a full refund. So "separate now, align later" is not reliable — do the two in the same flow.
Q: Do I need to separate a platform song myself first?
A: No. With vocal_separate: true we check whether the song already has a vocal track; if there is no usable vocal track it reports an error and refunds rather than fobbing you off with the full mix.
Q: Can I batch them? A: One song per request. For batches, loop; every call is charged normally, so caching results locally is recommended.
Q: Is the result saved? A: This endpoint does not persist anything; save the returned value yourself (both line-level and per-character timestamps are in the response).
Get an API key
Create an API key in the console after signing up. ¥0.6 per run returns 2 tracks, downloads are unlimited, and no phone number is required.
Get API key Back to overview