“先说结论:suno/generate 的任务提交和轮询都是正常工作的,但不要以为状态变成了 succeeded,就能直接把歌扔给用户了。”这是开发者从 Crazyrouter 的 Suno SDK 实测中拿到的第一手教训。当接口告诉你“生成成功”,并不意味着可播放的音频文件已经就绪,你还得亲自去验证下载链接的 HTTP 状态码、内容类型甚至文件大小。否则,轻则播放器哑火,重则用户以为你软件崩了。
这次测试的环境非常明确:时间戳固定在 2026 年 7 月 6 日,基准地址是 https://api.crazyrouter.com,用到的端点是 POST /v1/video/generations,模型则锁定为 suno/generate,且指定了 V5_5 版本。任务 ID task_ac90b23619334516,全程可追溯。从环境搭建到最终文件落地,每一步都踩出了实实在在的经验值。
起初照着文档的 input 样例去发请求,直接收到了一记“invalid_request”,错误信息里清楚写着“prompt is required”。也就是说,在当时的线上环境中,最外层的 prompt 字段是必填项,文档里那种“只塞 metadata”的写法根本跑不通。这种文档跟运行时表现的对不上号,很容易让新手在第一步就卡死,而官方文档却一脸无辜。
真正跑通的请求结构,是把 prompt 同时放在顶层和 metadata 里,还要配上 style、title 等参数。具体来说,顶层的 prompt 写入歌词,metadata 内部再重复一份 prompt,并声明 model_version、customMode、instrumental 等。返回的是一个异步任务对象,status 初始为“queued”,progress 为零。这就定调了:Suno 系的音乐生成不是同步 API,你创建的不是媒体文件,而是一份待执行的工作单。
接下来就是不断轮询 GET /v1/video/generations/{task_id},直到状态变成“succeeded”。最终拿到的响应里,code 为“success”,data 中的 status 是“succeeded”,format 显示为“mp4”,同时给出了一个公开的 content URL。从工作单元的角度看,这当然是成功。但如果你直接把那个 URL 抛给播放器,十有八九会扑空——这次实测中,该 content 地址连续返回 404 和 502,根本不稳定。
真正能用的音频地址藏在更深一层:data.data.resultJson.data[0].audio_url。只有这个字段对应的是一个真实的 MP3 文件,HTTP 200 响应,Content-Type 明确为 audio/mp3,且文件大小达到了 412326 字节。换句话说,succeeded 只是个任务级别的状态标志,它离“音频文件可下载”还存在一层关键的资源就绪判断,这层判断必须由开发者自己来完成。
基于上述踩坑过程,可以整理出一套最小可用的实现路径,每一步都带着血泪:
1. 向 /v1/video/generations 发送正确的请求,注意顶层必须携带 prompt。
2. 拿到响应后,立刻保存 task_id,这是后续所有操作的凭证。
3. 周期性地查询任务状态,直到 status 显示为 succeeded 才跳出轮询。
4. 不要直接信任公开的 content URL,而是去检查 resultJson.data[0].audio_url。
5. 对得到的音频 URL 发起 HEAD 或 GET 请求,确认返回 200、Content-Type 属于 audio/* 类型,并且文件大小在合理范围。
6. 如果业务需要,将音频流转存到自己的对象存储里,避免对上游接口产生持续依赖。
这六步走下来,suno/generate 才算真正“可用”,而不是仅仅“看起来可用”。
总的看,suno/generate 这条链路本身是通的,Crazyrouter 的异步工作流也没有本质缺陷。但它把“任务成功”和“资源就绪”这两个正交的状态强行揉在同一个 succeeded 标识符下,就给所有接入方埋下了一个极易误判的雷。最小的实现里,最关键的那一行代码,不是发请求,也不是轮询状态,而是在任务成功后,死磕 resultJson.data[0].audio_url——必须亲眼看到 200 和 audio/mp3,才敢对用户说“你的歌,好了”。
热门跟贴