Skip to main content
更新日期:2026-06-24

适用范围

这页文档讲的是 Crazyrouter 当前这套 Suno 统一任务模型族:
  • suno/generate
  • suno/extend
  • suno/upload-extend
  • suno/cover
  • suno/timestamped-lyrics
  • suno/vocal-separation
它们不是 OpenAI SDK 的内置方法,应该使用同一个 API Key,通过 Crazyrouter 原生异步任务路径调用:
POST /v1/video/generations
GET  /v1/video/generations/{task_id}
GET  /v1/videos/{task_id}/content
不要把这套 suno/* 模型当成 client.audio.*client.responses.* 来调。它们当前属于 Crazyrouter 原生任务接口,不是 OpenAI 官方 SDK 的标准音频方法。

基础地址

国际默认入口:
https://api.crazyrouter.com
如果你已经在业务里统一使用主站域名,也可以使用:
https://crazyrouter.com

鉴权

所有请求都使用同一个 Crazyrouter API Key:
Authorization: Bearer YOUR_API_KEY
如果你要固定打某个已验证渠道,例如 258,当前最稳妥的方式是把渠道后缀拼到 Key 后面:
Bearer YOUR_API_KEY-258
默认情况下可不指定,走自动路由。

版本参数

Suno 统一任务模型族使用 input.model_version,不是老 /suno/submit/music 路由里的 mv 当前可用版本值:
UI 文案model_version
V5.5 (Latest)V5_5
V5V5
V4.5 PlusV4_5PLUS
V4.5 AllV4_5ALL
V4.5V4_5
V4V4
2026-06-24 +08:00 本地对 258 渠道逐个实测,以上 6 个版本在 suno/generate 主链全部成功。

通用提交格式

所有模型都走同一个提交入口:
{
  "model": "suno/generate",
  "callBackUrl": "https://your-domain.com/callback",
  "channel": "auto",
  "input": {
    "model_version": "V5_5"
  }
}
其中:
  • model 是具体的 Suno 子模型名
  • callBackUrl 可选
  • channel 可选,建议默认 auto
  • 具体参数放在 input

查询任务

提交后拿到 task_id,再轮询:
curl https://api.crazyrouter.com/v1/video/generations/TASK_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
典型响应:
{
  "code": "success",
  "message": "",
  "data": {
    "status": "succeeded",
    "task_id": "task_xxx",
    "format": "mp4",
    "url": "https://api.crazyrouter.com/v1/videos/task_xxx/content",
    "metadata": null,
    "error": null
  }
}
下载最终内容:
curl -L https://api.crazyrouter.com/v1/videos/TASK_ID/content -o result.mp4
当前部分 Suno 路由查询结果只稳定返回 task_idstatusurl,不一定总能回出 audioId。如果你的链路依赖派生模型,请先确认上游结果里拿得到所需字段。

模型清单与请求示例

1. suno/generate

文本生成歌曲。 最常用参数:
  • model_version
  • prompt
  • customMode
  • instrumental
  • style
  • title
curl -X POST https://api.crazyrouter.com/v1/video/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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\n[Chorus]\nTest the route and let it sing\nClear and bright on everything",
      "style": "bright synth pop, short, clean vocals",
      "title": "CR Suno Route Test"
    }
  }'

2. suno/extend

基于已有 Suno 音频继续续写。 最常用参数:
  • model_version
  • audioId
  • continueAt
  • prompt
  • style
  • title
  • instrumental
{
  "model": "suno/extend",
  "channel": "auto",
  "input": {
    "model_version": "V5",
    "audioId": "abc123-def456",
    "continueAt": 20,
    "instrumental": false,
    "prompt": "[Bridge]\nCarry the melody forward with a brighter chorus and soft harmonies",
    "style": "bright synth pop, clean vocals",
    "title": "CR Suno Route Test Extended"
  }
}

3. suno/upload-extend

基于外部音频 URL 继续续写。 最常用参数:
  • model_version
  • audioUrl
  • continueAt
  • prompt
  • style
  • title
{
  "model": "suno/upload-extend",
  "channel": "auto",
  "input": {
    "model_version": "V4_5PLUS",
    "audioUrl": "https://your-storage.example.com/source.mp3",
    "continueAt": 20,
    "instrumental": false,
    "prompt": "Extend with a simple upbeat outro",
    "style": "bright synth pop",
    "title": "Upload Extend Demo"
  }
}

4. suno/cover

基于已有音频做翻唱或风格改写。 最常用参数:
  • model_version
  • audioUrl
  • customMode
  • instrumental
  • prompt
  • style
  • title
  • vocalGender
{
  "model": "suno/cover",
  "channel": "auto",
  "input": {
    "model_version": "V4_5ALL",
    "audioUrl": "https://your-storage.example.com/original.mp3",
    "customMode": false,
    "instrumental": false,
    "prompt": "Transform into an acoustic pop cover with gentle guitar",
    "style": "acoustic pop",
    "title": "Acoustic Cover Demo",
    "vocalGender": "f"
  }
}

5. suno/timestamped-lyrics

获取时间轴歌词。 最常用参数:
  • taskId
  • audioId
{
  "model": "suno/timestamped-lyrics",
  "channel": "auto",
  "input": {
    "taskId": "task_xxx",
    "audioId": "audio_xxx"
  }
}
当前 258 渠道在 2026-06-24 +08:00 的复测里,timestamped-lyrics 仍不稳定:metadata 写法会返回 400 invalid_requestinput 写法也出现过上游 502。如果你要上线这条能力,先单独做渠道级验证,不要默认它和 generate 一样稳定。

6. suno/vocal-separation

做人声与伴奏分离。 当前最稳妥的参数集合:
  • taskId
  • audioId
  • audioUrl
{
  "model": "suno/vocal-separation",
  "channel": "auto",
  "input": {
    "taskId": "task_xxx",
    "audioId": "audio_xxx",
    "audioUrl": "https://your-storage.example.com/source.mp3",
    "prompt": "Separate vocals and instrumental stems"
  }
}
我们在 2026-06-24 +08:00 的最小修复验证里确认:只传 audioUrl 不够稳,补上 taskId + audioId + audioUrl258 渠道成功。

JavaScript / TypeScript 完整示例

const apiKey = process.env.CRAZYROUTER_API_KEY;
const baseURL = process.env.CRAZYROUTER_BASE_URL ?? 'https://api.crazyrouter.com';

if (!apiKey) {
  throw new Error('Missing CRAZYROUTER_API_KEY');
}

async function crazyrouterFetch(path: string, init: RequestInit = {}) {
  const response = await fetch(`${baseURL}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: 'application/json',
      ...(init.body ? { 'Content-Type': 'application/json' } : {}),
      ...init.headers,
    },
  });

  const body = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`);
  }
  return body;
}

async function submitSunoGenerate() {
  const task = await crazyrouterFetch('/v1/video/generations', {
    method: 'POST',
    body: JSON.stringify({
      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\\n[Chorus]\\nTest the route and let it sing\\nClear and bright on everything',
        style: 'bright synth pop, short, clean vocals',
        title: 'CR Suno SDK Demo',
      },
    }),
  });

  const taskId = task.data?.task_id ?? task.task_id ?? task.id;
  if (!taskId) {
    throw new Error(`Missing task_id: ${JSON.stringify(task)}`);
  }
  return taskId;
}

async function waitForTask(taskId: string, timeoutMs = 10 * 60 * 1000) {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const result = await crazyrouterFetch(`/v1/video/generations/${encodeURIComponent(taskId)}`);
    const status = String(result.data?.status ?? result.status ?? '').toLowerCase();

    if (['succeeded', 'success', 'completed', 'done'].includes(status)) {
      return result;
    }
    if (['failed', 'failure', 'error', 'cancelled', 'canceled'].includes(status)) {
      throw new Error(`Task failed: ${JSON.stringify(result)}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 8000));
  }

  throw new Error(`Task timeout: ${taskId}`);
}

const taskId = await submitSunoGenerate();
console.log('taskId:', taskId);

const result = await waitForTask(taskId);
console.log('status:', result.data?.status);
console.log('content url:', result.data?.url);

Python 完整示例

import os
import time
import requests

BASE_URL = os.getenv("CRAZYROUTER_BASE_URL", "https://api.crazyrouter.com")
API_KEY = os.getenv("CRAZYROUTER_API_KEY")

if not API_KEY:
    raise RuntimeError("Missing CRAZYROUTER_API_KEY")

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

submit = requests.post(
    f"{BASE_URL}/v1/video/generations",
    headers=HEADERS,
    json={
        "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\\n[Chorus]\\nTest the route and let it sing\\nClear and bright on everything",
            "style": "bright synth pop, short, clean vocals",
            "title": "CR Suno Python Demo",
        },
    },
    timeout=120,
)
submit.raise_for_status()
task = submit.json()
task_id = task.get("data", {}).get("task_id") or task.get("task_id") or task.get("id")

if not task_id:
    raise RuntimeError(f"Missing task_id: {task}")

print("task_id:", task_id)

while True:
    resp = requests.get(
        f"{BASE_URL}/v1/video/generations/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}", "Accept": "application/json"},
        timeout=120,
    )
    resp.raise_for_status()
    data = resp.json()
    status = str(data.get("data", {}).get("status", "")).lower()
    print("status:", status)

    if status in {"succeeded", "success", "completed", "done"}:
        print("content url:", data.get("data", {}).get("url"))
        break
    if status in {"failed", "failure", "error", "cancelled", "canceled"}:
        raise RuntimeError(data)

    time.sleep(8)

常见问题

为什么我能生成,但拿不到 audioId

这通常不是你请求体的问题,而是当前渠道或上游在查询结果里没有把 Suno 原始音频项完整透出。主链 generate 成功,不代表所有派生能力都一定可用。

/suno/submit/music 和这里是什么关系?

老路由是 Crazyrouter 早期保留的 Suno 原生接口;这页写的是当前统一任务模型族,也就是 model: "suno/*" 配合 /v1/video/generations 的接法。新项目优先按这页方式接。

我应该传 mv 还是 model_version

这套统一任务模型族传 input.model_version。只有老 /suno/submit/music 才传 mv