AI 音乐生成完成后,音频 URL 为什么会过期?一套可恢复的交付方案
更新时间:2026-11-09
作者:Suno-API 工程团队
Suno-API 使用 Suno 官方 V6 模型。
直接答案
AI 音乐生成接口返回的音频地址,是为了尽快把文件交给调用方而生成的短期传输地址。它适合播放、下载和临时预览,但不适合作为产品的永久媒体资产。
可靠的媒体交付至少包含五个阶段:
- 确认歌曲任务已经完成;
- 获取对应类型的媒体地址;
- 下载并检查响应状态、文件类型和文件大小;
- 计算 SHA-256 并转存到自己的对象存储;
- 回读文件并验证哈希,再向用户标记交付完成。
如果旧地址过期,正确做法是重新获取下载地址,而不是重新生成歌曲。
为什么媒体 URL 会过期
公开文档说明,MP3、WAV、视频和封面等媒体地址默认 7 天有效。到期后,文件可能被回收,旧 URL 会返回 404。
URL 过期只表示临时传输地址失效,不代表:
- 歌曲生成失败;
- 原歌曲被删除;
- 用户必须再次付费生成;
song_id已经无效。
因此,业务数据库只保存一个 audio_url 是不够的。更好的做法是把"歌曲记录"和"媒体资产记录"分开。
一套推荐的数据结构
text
generation_request
request_id
idempotency_key
request_status
created_at
song
song_id
request_id
task_index
song_status
title
model
completed_at
media_asset
asset_id
song_id
kind
object_key
content_type
byte_size
checksum_sha256
source_url_fetched_at
stored_at
verified_at
其中:
song_id用于向平台查询歌曲;asset_id是业务系统自己的稳定资产 ID;object_key指向自己的对象存储;checksum_sha256用于判断文件是否完整;- 上游 URL 只保留获取时间和刷新状态,不建议记录完整签名参数。
获取地址和刷新地址
获取音频地址使用公开接口:
bash
curl -X POST https://www.suno-api.io/api/music/download-url \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"song_id":"SONG_ID","kind":"song","refresh":true}'
refresh=true 会要求重新准备媒体资源。地址过期后,可以再次调用该接口获取新地址。
需要区分两种失败:
| 状态 | 处理方式 |
|---|---|
| URL 404 | 刷新媒体地址,不重新生成歌曲 |
| 歌曲仍在 processing | 有界轮询,不重复提交生成 |
| 401 / 403 | 修正 Key 或权限,不自动重试 |
| 429 | 降低并发,等待后重试 |
| 5xx / 网络中断 | 指数退避,有最大次数 |
| 文件类型错误 | 拒绝入库并记录响应信息 |
下载和校验流程
不要直接从远端地址把文件流式转发给终端用户,也不要用户点击一次就重新下载一次。更稳妥的方式是:
- 从媒体地址下载到临时文件;
- 检查 HTTP 状态和
Content-Type; - 一边下载一边计算 SHA-256;
- 先写入
.part临时文件; - 校验完成后再原子改名;
- 上传到自己的对象存储;
- 回读并比较文件大小和哈希;
- 写入稳定的
asset_id和对象存储 Key。
Python 参考实现
python
import hashlib
import os
from pathlib import Path
import requests
BASE_URL = "https://www.suno-api.io"
API_KEY = os.environ["SUNO_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def refresh_media_url(song_id: str, kind: str = "song") -> str:
response = requests.post(
f"{BASE_URL}/api/music/download-url",
headers={**HEADERS, "Content-Type": "application/json"},
json={"song_id": song_id, "kind": kind, "refresh": True},
timeout=60,
)
response.raise_for_status()
return response.json()["data"]["url"]
def download_and_hash(url: str, destination: Path) -> str:
destination.parent.mkdir(parents=True, exist_ok=True)
temporary = destination.with_suffix(destination.suffix + ".part")
digest = hashlib.sha256()
with requests.get(url, stream=True, timeout=(15, 180)) as response:
response.raise_for_status()
if not response.headers.get("Content-Type"):
raise RuntimeError("missing Content-Type")
with temporary.open("wb") as output:
for chunk in response.iter_content(chunk_size=1024 * 1024):
if chunk:
digest.update(chunk)
output.write(chunk)
os.replace(temporary, destination)
return digest.hexdigest()
生产环境还应限制最大文件大小,记录解析后的媒体类型,并把成功校验的文件上传到自己的对象存储。
Node.js 参考实现
javascript
import { createHash } from "node:crypto";
import { createWriteStream } from "node:fs";
import { rename } from "node:fs/promises";
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
async function downloadAndHash(url, destination) {
const response = await fetch(url, {
signal: AbortSignal.timeout(180_000),
});
if (!response.ok || !response.body) {
throw new Error(`media download failed: ${response.status}`);
}
const hash = createHash("sha256");
const temporary = `${destination}.part`;
const hasher = new Transform({
transform(chunk, _encoding, callback) {
hash.update(chunk);
callback(null, chunk);
},
});
await pipeline(
Readable.fromWeb(response.body),
hasher,
createWriteStream(temporary),
);
await rename(temporary, destination);
return hash.digest("hex");
}
URL 过期后的恢复顺序
推荐按以下顺序处理:
- 用相同
song_id和kind请求新的下载地址; - 获取成功后重新下载并校验;
- 如果歌曲不存在,检查账号归属和
song_id来源; - 如果歌曲仍在生成,进入有界轮询;
- 如果任务失败,将业务状态改为失败终态;
- 不因为媒体地址过期而新建生成订单。
上线验收清单
- 完成歌曲能获取到对应媒体类型;
- 下载响应有正确状态码和 Content-Type;
- 使用临时文件写入,避免半文件入库;
- 下载过程计算 SHA-256;
- 文件转存到自有对象存储;
- 回读文件并核对大小与哈希;
- 数据库保存稳定资产 ID 和对象存储 Key;
- URL 过期后能刷新,不重新生成歌曲;
- 日志不记录完整 Key 和带签名的媒体 URL;
- 401、403、429、5xx、URL 过期和任务失败能分别处理。
延伸阅读
- GitHub 完整指南: https://github.com/suno-api-io/suno-api-python-starter/blob/main/guides/media-url-lifecycle.md
- GitHub 完整接口文档: https://github.com/suno-api-io/suno-api-python-starter/blob/main/docs/README.md
- 官网接口文档: https://www.suno-api.io/docs/
- 媒体下载 API: https://www.suno-api.io/docs/current-music-media-download/
- 歌曲查询 API: https://www.suno-api.io/docs/current-music-query/