【Suno SDK 系列 01】Crazyrouter Suno SDK 怎么接入?从 0 提交一个 suno/generate 任务,附 2026-07-06 实测

发布时间:2026/7/7 13:29:45
【Suno SDK 系列 01】Crazyrouter Suno SDK 怎么接入?从 0 提交一个 suno/generate 任务,附 2026-07-06 实测 【Suno SDK 系列 01】Crazyrouter Suno SDK 怎么接入从 0 提交一个 suno/generate 任务附 2026-07-06 实测适合读者后端开发、AI 工具开发者、正在接入 AI 音乐生成能力的 SaaS 团队。文章重点不是只复制文档示例而是按真实接口跑一遍submit - task_id - poll - content url - audio_url并记录本次遇到的请求体差异。先说结论我在2026-07-06 08:00按 Crazyrouter 的 Suno SDK 文档做了一次最小实测。文档地址https://docs.crazyrouter.com/sdks/suno-sdk本次测的是最基础的歌曲生成模型Base URL: https://api.crazyrouter.com Endpoint: POST /v1/video/generations Model: suno/generate Version: V5_5实测结果如下步骤请求结果文档input写法提交POST /v1/video/generations返回prompt is required顶层promptmetadata写法提交POST /v1/video/generations返回task_id状态queued查询任务GET /v1/video/generations/{task_id}返回succeeded旧格式显示mp4下载公开内容 URLGET /v1/videos/{task_id}/content原先返回 404/502代理未拿到媒体文件下载真实媒体 URLresultJson.data[0].audio_urlHTTP 200audio/mp3412326 bytes所以这篇文章会给出一个更谨慎的结论suno/generate 的任务提交和轮询链路可以跑通 但生产判断不能停在 succeeded必须继续验证 content url 或真实 audio_url 是否真的返回媒体文件。这点很重要。音频/视频/音乐生成都是异步任务HTTP 200、queued、succeeded分别代表不同阶段不能混在一起。1. Suno SDK 这套接口到底是什么Crazyrouter 文档里的 Suno SDK 指的是一组 Suno 统一任务模型族不是 OpenAI 官方 SDK 里的client.audio.*。当前文档列出的模型包括suno/generate suno/extend suno/upload-extend suno/cover suno/timestamped-lyrics suno/vocal-separation统一任务入口是POST /v1/video/generations GET /v1/video/generations/{task_id} GET /v1/videos/{task_id}/content也就是说它的调用方式更像“提交一个异步任务”而不是“调用一个同步音频生成函数”。推荐你按下面这个思路理解提交任务告诉服务端我要生成什么音乐 拿 task_id服务端确认任务已经进入队列 轮询任务等待上游真正生成完成 下载内容拿到最终 mp3/audio 内容并校验2. 准备 API Key 和环境变量不要把 API Key 写死在代码里。建议先放到环境变量。Linux / macOSexportCRAZYROUTER_API_KEY你的 Crazyrouter API KeyPowerShell$env:CRAZYROUTER_API_KEY你的 Crazyrouter API Key本文使用的 Base URLhttps://api.crazyrouter.com文档也提到如果业务里已经统一使用主站域名也可以用https://crazyrouter.com我本次两个域名都测了后面会说差异。3. 第一次按文档 input 写法提交失败文档里的通用示例大致是这种结构{model:suno/generate,channel:auto,input:{model_version:V5_5,customMode:true,instrumental:false,prompt:[Verse]\nMorning lights across the wire\nTiny sparks climb higher,style:bright synth pop, short, clean vocals,title:CR Suno SDK Demo}}我用中文歌词做了一个最小请求{model:suno/generate,channel:auto,input:{model_version:V5_5,customMode:true,instrumental:false,prompt:[Verse]\n清晨的键盘亮起光\n请求穿过云端的墙\n[Chorus]\n用一次实测把链路唱响\n从任务到结果都清清爽爽,style:bright synth pop, short, clean vocals, Mandarin pop,title:Crazyrouter Suno SDK Smoke Test 20260706}}提交命令curl-sS-XPOST https://api.crazyrouter.com/v1/video/generations\-HAuthorization: Bearer$CRAZYROUTER_API_KEY\-HContent-Type: application/json\--data-binary suno_sdk_generate_payload_20260706.json返回{code:invalid_request,message:prompt is required,data:null}我又把域名换成https://crazyrouter.com/v1/video/generations返回仍然是{code:invalid_request,message:prompt is required,data:null}这说明它不是单一域名导致的问题而是当前生产接口对顶层prompt有校验。4. 第二次用顶层 prompt metadata提交成功本地历史生产测试脚本里使用过一种兼容写法{model:suno/generate,prompt:这里放主提示词,metadata:{model_version:V5_5,customMode:true,instrumental:false,prompt:这里也放完整歌词或描述,style:bright synth pop, short, clean vocals, Mandarin pop,title:Crazyrouter Suno SDK Smoke Test 20260706}}完整请求体{model:suno/generate,prompt:[Verse]\n清晨的键盘亮起光\n请求穿过云端的墙\n[Chorus]\n用一次实测把链路唱响\n从任务到结果都清清爽爽,metadata:{model_version:V5_5,customMode:true,instrumental:false,prompt:[Verse]\n清晨的键盘亮起光\n请求穿过云端的墙\n[Chorus]\n用一次实测把链路唱响\n从任务到结果都清清爽爽,style:bright synth pop, short, clean vocals, Mandarin pop,title:Crazyrouter Suno SDK Smoke Test 20260706}}提交curl-sS-XPOST https://api.crazyrouter.com/v1/video/generations\-HAuthorization: Bearer$CRAZYROUTER_API_KEY\-HContent-Type: application/json\--data-binary suno_sdk_generate_payload_metadata_20260706.json返回{id:task_ac90b23619334516,task_id:task_ac90b23619334516,object:video,model:suno/generate,status:queued,progress:0,created_at:1783267658}这里要注意queued只代表任务提交成功还没有生成完成。5. 轮询任务状态拿到task_id后开始查curl-sShttps://api.crazyrouter.com/v1/video/generations/task_ac90b23619334516\-HAuthorization: Bearer$CRAZYROUTER_API_KEY本次轮询结果{code:success,message:,data:{error:null,format:mp4,metadata:null,status:succeeded,task_id:task_ac90b23619334516,url:https://crazyrouter.com/v1/videos/task_ac90b23619334516/content}}到这里任务层面的结果是成功的status: succeeded format: mp4 url: https://crazyrouter.com/v1/videos/task_ac90b23619334516/content但是注意url出现不等于内容已经成功下载。下一步还要校验媒体文件。6. JavaScript / TypeScript 最小示例下面这段代码把提交和轮询合在一起。这里我保留顶层prompt metadata写法因为这是本次实测能通过生产接口校验的结构。constapiKeyprocess.env.CRAZYROUTER_API_KEY;constbaseURLprocess.env.CRAZYROUTER_BASE_URL??https://api.crazyrouter.com;if(!apiKey){thrownewError(Missing CRAZYROUTER_API_KEY);}asyncfunctionrequest(path:string,init:RequestInit{}){constresponseawaitfetch(${baseURL}${path},{...init,headers:{Authorization:Bearer${apiKey},Accept:application/json,...(init.body?{Content-Type:application/json}:{}),...init.headers,},});constbodyawaitresponse.json().catch(()({}));if(!response.ok){thrownewError(HTTP${response.status}:${JSON.stringify(body)});}returnbody;}asyncfunctionsubmitSunoGenerate(){constprompt[Verse]\n清晨的键盘亮起光\n请求穿过云端的墙\n[Chorus]\n用一次实测把链路唱响\n从任务到结果都清清爽爽;consttaskawaitrequest(/v1/video/generations,{method:POST,body:JSON.stringify({model:suno/generate,prompt,metadata:{model_version:V5_5,customMode:true,instrumental:false,prompt,style:bright synth pop, short, clean vocals, Mandarin pop,title:Crazyrouter Suno SDK Demo,},}),});consttaskIdtask.data?.task_id??task.task_id??task.id;if(!taskId){thrownewError(Missing task_id:${JSON.stringify(task)});}returntaskId;}asyncfunctionwaitForTask(taskId:string,timeoutMs10*60*1000){constdeadlineDate.now()timeoutMs;while(Date.now()deadline){constresultawaitrequest(/v1/video/generations/${encodeURIComponent(taskId)});conststatusString(result.data?.status??result.status??).toLowerCase();console.log(status:,status);if([succeeded,success,completed,done].includes(status)){returnresult;}if([failed,failure,error,cancelled,canceled].includes(status)){thrownewError(Task failed:${JSON.stringify(result)});}awaitnewPromise((resolve)setTimeout(resolve,8000));}thrownewError(Task timeout:${taskId});}consttaskIdawaitsubmitSunoGenerate();console.log(taskId:,taskId);constresultawaitwaitForTask(taskId);console.log(final:,result.data);7. Python 最小示例Python 版本如下importosimporttimeimportrequests BASE_URLos.getenv(CRAZYROUTER_BASE_URL,https://api.crazyrouter.com)API_KEYos.getenv(CRAZYROUTER_API_KEY)ifnotAPI_KEY:raiseRuntimeError(Missing CRAZYROUTER_API_KEY)headers{Authorization:fBearer{API_KEY},Content-Type:application/json,Accept:application/json,}prompt[Verse]\n清晨的键盘亮起光\n请求穿过云端的墙\n[Chorus]\n用一次实测把链路唱响\n从任务到结果都清清爽爽submitrequests.post(f{BASE_URL}/v1/video/generations,headersheaders,json{model:suno/generate,prompt:prompt,metadata:{model_version:V5_5,customMode:True,instrumental:False,prompt:prompt,style:bright synth pop, short, clean vocals, Mandarin pop,title:Crazyrouter Suno SDK Demo,},},timeout120,)submit.raise_for_status()tasksubmit.json()task_idtask.get(data,{}).get(task_id)ortask.get(task_id)ortask.get(id)ifnottask_id:raiseRuntimeError(fMissing task_id:{task})print(task_id:,task_id)whileTrue:responserequests.get(f{BASE_URL}/v1/video/generations/{task_id},headers{Authorization:fBearer{API_KEY},Accept:application/json},timeout120,)response.raise_for_status()dataresponse.json()statusstr(data.get(data,{}).get(status,)).lower()print(status:,status)ifstatusin{succeeded,success,completed,done}:print(content url:,data.get(data,{}).get(url))breakifstatusin{failed,failure,error,cancelled,canceled}:raiseRuntimeError(data)time.sleep(8)8. 这次实测暴露的两个坑坑一文档示例和当前生产校验可能不完全一致文档里写的是{model:suno/generate,channel:auto,input:{prompt:...}}但我在2026-07-06 08:00用两个域名实测均返回{code:invalid_request,message:prompt is required,data:null}兼容写法是{model:suno/generate,prompt:...,metadata:{prompt:...,model_version:V5_5}}如果你上线前只照抄文档很可能会卡在第一步。坑二succeeded 不代表公开 content URL 一定可下载本次查询接口返回statussucceeded formatmp4 urlhttps://crazyrouter.com/v1/videos/task_ac90b23619334516/content但公开内容 URL 校验时HEAD crazyrouter.com content URL: 404 GET api.crazyrouter.com content URL Authorization: 502继续追上游原始结果后真实媒体地址是resultJson.data[0].audio_url直链下载成功HTTP 200 Content-Type: audio/mp3 Downloaded: 412326 bytes所以严谨的生产判断应该是submit 成功 task_id 存在 poll 到 succeeded content url 或 audio_url 返回 HTTP 200 Content-Type 是 audio/* 文件大小明显大于错误页 可播放或可被 ffprobe 识别9. 最小接入检查清单第一次接入时建议按这个清单排查CRAZYROUTER_API_KEY是否存在。Base URL 是否是https://api.crazyrouter.com或https://crazyrouter.com。endpoint 是否是/v1/video/generations不是/v1/chat/completions。model是否明确传了suno/generate。是否有顶层prompt。model_version是否使用V5_5、V5、V4_5PLUS等文档列出的枚举值。是否拿到了task_id。是否轮询到终态。是否验证了内容 URL 或resultJson.data[0].audio_url。是否记录了失败响应体和 request id。10. FAQQ1Suno SDK 可以用 OpenAI SDK 直接调吗不建议按client.audio.*或client.responses.*来理解。文档明确说这套suno/*当前属于 Crazyrouter 原生任务接口应该走/v1/video/generations。Q2为什么是 video/generations不是 audio/generations这是 Crazyrouter 当前统一任务接口命名。旧查询结果里返回formatmp4但本次真实audio_url下载到的是audio/mp3。接口路径叫 video 不影响它作为音乐生成任务使用最终格式要看实际媒体 URL 和Content-Type。Q3我应该用input还是metadata文档展示的是input。但我在2026-07-06的生产实测中纯input写法返回prompt is required顶层prompt metadata写法可以提交成功。上线前建议你用自己的 API Key 对当前生产接口复测。Q4拿到succeeded后是不是就可以给用户展示下载按钮不建议。还要验证内容 URL 能否下载到真实媒体文件。本次实测是任务succeeded后公开/content代理未拿到媒体文件继续解析resultJson.data[0].audio_url后真实音频文件可以下载。最后总结如果你只是想先跑通 Suno SDK最小路径是POST /v1/video/generations modelsuno/generate 顶层 prompt metadata 拿 task_id 轮询 /v1/video/generations/{task_id} 继续验证 /v1/videos/{task_id}/content 或 resultJson.data[0].audio_url这篇的重点不是证明“所有能力都稳定”而是把接入的第一步跑清楚提交任务和查询任务可以跑通但真正上线还必须做内容 URL 或audio_url校验。下一篇我会专门讲异步任务轮询、内容下载、404/502 的排查方式以及如何从resultJson.data[0].audio_url找到真正可下载的 Suno 结果。