{
  "openapi": "3.1.0",
  "info": {
    "title": "Suno Gateway REST API",
    "version": "v1",
    "description": "Submit asynchronous Suno work — music generation, extension, covers and lyrics — through a multi-account gateway. The gateway owns account selection, upstream credits, per-tier concurrency limits and routing. Your service owns customer logic; every submission returns a server-generated `taskId` you then poll or receive by callback.\n\n## Authentication\n\nEvery request must carry a service API key as a bearer token:\n\n```\nAuthorization: Bearer <SERVICE_API_KEY>\n```\n\nKeys are issued per calling service on the private admin dashboard (`:8090`, token-protected). Tasks are scoped to the issuing key: a service can only see its own `taskId`s — unknown ids and ids owned by other services both return `404`.\n\n## Response envelope\n\nEvery response — success or error — is wrapped in `{code, msg, data}`:\n\n| Field | Meaning |\n|---|---|\n| `code` | Always equal to the HTTP status code (`200` on success). |\n| `msg` | `\"success\"` on success; a human-readable reason on errors. |\n| `data` | The endpoint payload. Omitted on errors. |\n\n## Task statuses\n\nTasks are asynchronous. `record-info` and callbacks report one of:\n\n| Status | Terminal | Meaning |\n|---|---|---|\n| `PENDING` | no | Queued, routing, or being submitted to Suno. |\n| `GENERATING` | no | Submitted; audio is being rendered upstream. |\n| `SUCCESS` | yes | Music task finished; `response.sunoData` holds the tracks. |\n| `TEXT_SUCCESS` | yes | Lyrics task finished; `response.text` holds the lyrics. |\n| `GENERATE_AUDIO_FAILED` | yes | Music task failed; see `errorCode` / `errorMessage`. |\n| `CREATE_TASK_FAILED` | yes | Submission failed or its outcome is unknown, or a lyrics task failed. |\n\nPoll until a terminal status, then stop. Generation typically takes 1–3 minutes depending on model and upstream queue depth.\n\n## Callbacks\n\nPass `callBackUrl` on any submit request and the gateway POSTs the final task state to it once, when the task reaches a terminal status. Callbacks are **optional on every endpoint** — omitting one simply means you poll instead. Delivery is best effort with a 15-second timeout and no retry: any failure or non-2xx response loses the push, so poll by `taskId` as the reliable path. The callback body is the standard envelope whose `data` is the exact record-info shape.\n\n## Typical workflow\n\n1. `GET /api/v1/models` — pick a model `key` that has capacity.\n2. `POST /api/v1/generate` (or extend / cover / lyrics) — receive `{code:200, msg:\"success\", data:{taskId}}`.\n3. Poll `GET /api/v1/generate/record-info?taskId=…`, or wait for the callback."
  },
  "servers": [
    { "url": "/", "description": "This gateway (same origin as the docs)" }
  ],
  "security": [
    { "ServiceBearer": [] }
  ],
  "tags": [
    { "name": "Generation", "description": "Submit asynchronous music and lyrics work." },
    { "name": "Retrieval", "description": "Poll task state and discover capacity." }
  ],
  "paths": {
    "/api/v1/generate": {
      "post": {
        "tags": ["Generation"],
        "operationId": "generateMusic",
        "summary": "Generate music from a text prompt",
        "description": "Submit a text-to-music generation. Each submission creates a new task and returns its `taskId` immediately; the result arrives via polling or callback.\n\nValidation:\n\n- `prompt` and `model` are always required.\n- `customMode: true` requires non-empty `style` **and** `title`; `prompt` is then treated as lyrics text.\n- `callBackUrl` is optional — omit it to rely on polling only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/GenerateRequest" },
              "examples": {
                "simple": {
                  "summary": "Simple description",
                  "value": {
                    "prompt": "a short upbeat whistling jingle",
                    "model": "chirp-crow",
                    "callBackUrl": "https://my-service.example.com/suno/callback"
                  }
                },
                "custom": {
                  "summary": "Custom mode with style and lyrics",
                  "value": {
                    "customMode": true,
                    "title": "Coastline",
                    "style": "dreamy synth-pop, female vocal",
                    "prompt": "[Verse 1]\nSalt in the air...",
                    "model": "chirp-fenix",
                    "negativeTags": "metal, distortion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubmitEnvelope" },
                "examples": {
                  "accepted": {
                    "value": { "code": 200, "msg": "success", "data": { "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "503": { "$ref": "#/components/responses/QueueUnavailable" }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Final task state",
                "description": "POSTed once when the task reaches a terminal status. Best effort, 15-second timeout, no retry; any non-2xx response is treated as rejection.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": { "$ref": "#/components/schemas/RecordInfoEnvelope" },
                      "examples": {
                        "success": {
                          "summary": "Completed music task",
                          "value": {
                            "code": 200,
                            "msg": "success",
                            "data": {
                              "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                              "status": "SUCCESS",
                              "type": "GENERATE",
                              "param": "{\"customMode\":false,\"instrumental\":false,\"prompt\":\"a short upbeat whistling jingle\"}",
                              "response": {
                                "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                                "sunoData": [
                                  {
                                    "id": "95363029-a760-4a9a-98e7-33dbadc07be0",
                                    "title": "",
                                    "audioUrl": "https://cdn1.suno.ai/95363029-a760-4a9a-98e7-33dbadc07be0.mp3",
                                    "streamAudioUrl": "https://cdn1.suno.ai/95363029-a760-4a9a-98e7-33dbadc07be0.mp3",
                                    "imageUrl": "https://cdn2.suno.ai/image_95363029-a760-4a9a-98e7-33dbadc07be0.jpeg",
                                    "modelName": "chirp-crow"
                                  }
                                ]
                              },
                              "errorCode": null,
                              "errorMessage": null
                            }
                          }
                        },
                        "failed": {
                          "summary": "Failed task",
                          "value": {
                            "code": 200,
                            "msg": "success",
                            "data": {
                              "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                              "status": "GENERATE_AUDIO_FAILED",
                              "type": "GENERATE",
                              "param": "{\"customMode\":false,\"instrumental\":false,\"prompt\":\"a short upbeat whistling jingle\"}",
                              "response": { "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999", "sunoData": [] },
                              "errorCode": "upstream_error",
                              "errorMessage": "generation failed upstream"
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "responses": {
                  "200": { "description": "Callback acknowledged." }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/extend": {
      "post": {
        "tags": ["Generation"],
        "operationId": "extendMusic",
        "summary": "Extend a clip previously created through this gateway",
        "description": "Continue an existing clip. The clip's recorded owner account is reused automatically — you never pick an account.\n\nValidation:\n\n- `audioId` and `model` are required.\n- `continueAt`, when given, must not be negative; omit it to continue from the end of the clip.\n- `customMode: true` requires `style`, `title` **and** `prompt`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ExtendRequest" },
              "examples": {
                "continueFromEnd": {
                  "summary": "Continue from the end",
                  "value": {
                    "audioId": "95363029-a760-4a9a-98e7-33dbadc07be0",
                    "model": "chirp-crow"
                  }
                },
                "continueAtOffset": {
                  "summary": "Continue from second 30.5",
                  "value": {
                    "audioId": "95363029-a760-4a9a-98e7-33dbadc07be0",
                    "model": "chirp-crow",
                    "continueAt": 30.5
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubmitEnvelope" },
                "examples": {
                  "accepted": {
                    "value": { "code": 200, "msg": "success", "data": { "taskId": "e2f5c8a1-91d2-4b7e-8f03-2a6d9c4e1b57" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "503": { "$ref": "#/components/responses/QueueUnavailable" }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Final task state",
                "description": "Same shape and semantics as the generate callback: the envelope whose `data` is the record-info shape, POSTed once at the terminal status.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": { "$ref": "#/components/schemas/RecordInfoEnvelope" }
                    }
                  }
                },
                "responses": {
                  "200": { "description": "Callback acknowledged." }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/cover": {
      "post": {
        "tags": ["Generation"],
        "operationId": "coverMusic",
        "summary": "Restyle a clip previously created through this gateway",
        "description": "Cover an existing clip. Unlike the reference API's `upload-cover`, the source is a clip already created through this gateway, referenced by `audioId` — there is no upload step.\n\nValidation:\n\n- `audioId` and `model` are required.\n- `startS` and `endS` must be supplied together.\n- `endS` must be strictly after `startS`.\n- `customMode: true` requires `style`, `title` **and** `prompt`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CoverRequest" },
              "examples": {
                "windowed": {
                  "summary": "Cover a 12–40s window",
                  "value": {
                    "audioId": "95363029-a760-4a9a-98e7-33dbadc07be0",
                    "model": "chirp-fenix",
                    "startS": 12,
                    "endS": 40,
                    "style": "bossa nova"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubmitEnvelope" },
                "examples": {
                  "accepted": {
                    "value": { "code": 200, "msg": "success", "data": { "taskId": "7d0a3b9e-4c1f-4e8a-b5d2-6f9e0c3a8b41" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "503": { "$ref": "#/components/responses/QueueUnavailable" }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Final task state",
                "description": "Same shape and semantics as the generate callback.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": { "$ref": "#/components/schemas/RecordInfoEnvelope" }
                    }
                  }
                },
                "responses": {
                  "200": { "description": "Callback acknowledged." }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/lyrics": {
      "post": {
        "tags": ["Generation"],
        "operationId": "generateLyrics",
        "summary": "Generate lyrics without submitting a music generation",
        "description": "Generate lyrics only — no music submission. Costs no audio credits.\n\nPoll with `GET /api/v1/lyrics/record-info`; the terminal status is `TEXT_SUCCESS` and the lyrics live in `response.text`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LyricsRequest" },
              "examples": {
                "theme": {
                  "value": { "prompt": "bittersweet summer song about a coastal town" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubmitEnvelope" },
                "examples": {
                  "accepted": {
                    "value": { "code": 200, "msg": "success", "data": { "taskId": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13" } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "503": { "$ref": "#/components/responses/QueueUnavailable" }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Final lyrics state",
                "description": "POSTed once at the terminal status (`TEXT_SUCCESS` or `CREATE_TASK_FAILED`). The body is the envelope whose `data` is the lyrics record-info shape.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": { "$ref": "#/components/schemas/RecordInfoEnvelope" },
                      "examples": {
                        "textSuccess": {
                          "value": {
                            "code": 200,
                            "msg": "success",
                            "data": {
                              "taskId": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13",
                              "status": "TEXT_SUCCESS",
                              "type": "LYRICS",
                              "param": "{\"prompt\":\"bittersweet summer song about a coastal town\"}",
                              "response": {
                                "taskId": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13",
                                "text": "[Verse 1]\nSalt in the air..."
                              },
                              "errorCode": null,
                              "errorMessage": null
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "responses": {
                  "200": { "description": "Callback acknowledged." }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/record-info": {
      "get": {
        "tags": ["Retrieval"],
        "operationId": "musicRecordInfo",
        "summary": "Poll a music task by taskId",
        "description": "Fetch the current state of a music task (generate, extend or cover). One submission yields two tracks; both are listed in `response.sunoData` once the status is `SUCCESS`.\n\nUnknown `taskId`s and tasks owned by other services both return `404` — the gateway never reveals whether another service's task exists.",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "description": "Task id returned by a submit endpoint.",
            "schema": { "type": "string" },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          }
        ],
        "responses": {
          "200": {
            "description": "Task state.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RecordInfoEnvelope" },
                "examples": {
                  "success": {
                    "summary": "Completed task with two tracks",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "status": "SUCCESS",
                        "type": "GENERATE",
                        "param": "{\"customMode\":false,\"instrumental\":false,\"prompt\":\"a short upbeat whistling jingle\"}",
                        "response": {
                          "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                          "sunoData": [
                            {
                              "id": "95363029-a760-4a9a-98e7-33dbadc07be0",
                              "title": "",
                              "audioUrl": "https://cdn1.suno.ai/95363029-a760-4a9a-98e7-33dbadc07be0.mp3",
                              "streamAudioUrl": "https://cdn1.suno.ai/95363029-a760-4a9a-98e7-33dbadc07be0.mp3",
                              "imageUrl": "https://cdn2.suno.ai/image_95363029-a760-4a9a-98e7-33dbadc07be0.jpeg",
                              "modelName": "chirp-crow"
                            },
                            {
                              "id": "858af1e2-e288-45a6-846c-24f1409a432e",
                              "title": "",
                              "audioUrl": "https://cdn1.suno.ai/858af1e2-e288-45a6-846c-24f1409a432e.mp3",
                              "streamAudioUrl": "https://cdn1.suno.ai/858af1e2-e288-45a6-846c-24f1409a432e.mp3",
                              "imageUrl": "https://cdn2.suno.ai/image_858af1e2-e288-45a6-846c-24f1409a432e.jpeg",
                              "modelName": "chirp-crow"
                            }
                          ]
                        },
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  },
                  "pending": {
                    "summary": "Still running",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "status": "GENERATING",
                        "type": "GENERATE",
                        "param": "{\"customMode\":false,\"instrumental\":false,\"prompt\":\"a short upbeat whistling jingle\"}",
                        "response": { "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999", "sunoData": [] },
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/lyrics/record-info": {
      "get": {
        "tags": ["Retrieval"],
        "operationId": "lyricsRecordInfo",
        "summary": "Poll a lyrics task by taskId",
        "description": "Fetch the current state of a lyrics task. Same envelope and status rules as music record-info; the payload is text instead of tracks — on `TEXT_SUCCESS` the lyrics live in `response.text`.",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "description": "Task id returned by POST /api/v1/lyrics.",
            "schema": { "type": "string" },
            "example": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13"
          }
        ],
        "responses": {
          "200": {
            "description": "Task state.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RecordInfoEnvelope" },
                "examples": {
                  "textSuccess": {
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13",
                        "status": "TEXT_SUCCESS",
                        "type": "LYRICS",
                        "param": "{\"prompt\":\"bittersweet summer song about a coastal town\"}",
                        "response": {
                          "taskId": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13",
                          "text": "[Verse 1]\nSalt in the air..."
                        },
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/models": {
      "get": {
        "tags": ["Retrieval"],
        "operationId": "listModels",
        "summary": "List models currently backed by at least one usable account",
        "description": "Use `key` as the `model` value on submit endpoints. Custom per-account models appear as `chirp-custom:<uuid>`.",
        "responses": {
          "200": {
            "description": "Available models.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ModelsEnvelope" },
                "examples": {
                  "default": {
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "models": [
                          { "key": "chirp-crow", "label": "v5", "accountCount": 1 },
                          { "key": "chirp-fenix", "label": "v5.5", "accountCount": 1 },
                          { "key": "chirp-auk-turbo", "label": "v4.5-all", "accountCount": 2 }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ServiceBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Paste a service API key. Requests use `Authorization: Bearer YOUR_KEY`. Keys are issued per calling service on the private admin dashboard."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" },
            "examples": {
              "missingFields": { "value": { "code": 400, "msg": "prompt and model are required" } },
              "customMode": { "value": { "code": 400, "msg": "custom mode requires style and title" } },
              "customModePrompt": { "value": { "code": 400, "msg": "custom mode requires a prompt" } },
              "negativeContinueAt": { "value": { "code": 400, "msg": "continueAt cannot be negative" } },
              "coverWindow": { "value": { "code": 400, "msg": "startS and endS must be supplied together" } },
              "coverWindowOrder": { "value": { "code": 400, "msg": "endS must be after startS" } },
              "missingTaskId": { "value": { "code": 400, "msg": "taskId is required" } },
              "badJson": { "value": { "code": 400, "msg": "invalid JSON body: unexpected EOF" } }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid service token.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" },
            "examples": {
              "default": { "value": { "code": 401, "msg": "missing or invalid service token" } }
            }
          }
        }
      },
      "NotFound": {
        "description": "Unknown taskId, or owned by another service.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" },
            "examples": {
              "default": { "value": { "code": 404, "msg": "task not found" } }
            }
          }
        }
      },
      "QueueUnavailable": {
        "description": "Task persisted but the queue is temporarily unavailable; poll by taskId after retrying. The task still runs once the queue recovers — do not blindly resubmit.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" },
            "examples": {
              "default": { "value": { "code": 503, "msg": "task persisted but the queue is temporarily unavailable; poll by taskId after retrying" } }
            }
          }
        }
      }
    },
    "schemas": {
      "GenerateRequest": {
        "type": "object",
        "required": ["prompt", "model"],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Free-form description of the song. In custom mode this is the lyrics text instead.",
            "example": "a short upbeat whistling jingle"
          },
          "model": {
            "type": "string",
            "description": "Model `key` from `GET /api/v1/models`, e.g. `chirp-crow` (v5).",
            "example": "chirp-crow"
          },
          "customMode": {
            "type": "boolean",
            "default": false,
            "description": "When true, `style` and `title` become required, and `prompt` is treated as lyrics text."
          },
          "style": {
            "type": "string",
            "description": "Style/genre tags. Required when `customMode` is true.",
            "example": "dreamy synth-pop, female vocal"
          },
          "title": {
            "type": "string",
            "description": "Track title. Required when `customMode` is true.",
            "example": "Coastline"
          },
          "instrumental": {
            "type": "boolean",
            "default": false,
            "description": "Generate without vocals."
          },
          "negativeTags": {
            "type": "string",
            "description": "Comma-separated tags to steer away from.",
            "example": "metal, distortion"
          },
          "callBackUrl": {
            "type": "string",
            "description": "Optional URL receiving one POST of the final record-info shape. Omit to use polling only.",
            "example": "https://my-service.example.com/suno/callback"
          }
        }
      },
      "ExtendRequest": {
        "type": "object",
        "required": ["audioId", "model"],
        "properties": {
          "audioId": {
            "type": "string",
            "description": "Source clip `id` from `response.sunoData[]`. Its recorded owner account is used for this operation.",
            "example": "95363029-a760-4a9a-98e7-33dbadc07be0"
          },
          "model": {
            "type": "string",
            "description": "Model `key` from `GET /api/v1/models`.",
            "example": "chirp-crow"
          },
          "continueAt": {
            "type": "number",
            "description": "Second to continue from. Must be >= 0; omit to continue from the end.",
            "example": 30.5
          },
          "prompt": {
            "type": "string",
            "description": "Lyrics for the continued section. Required when `customMode` is true."
          },
          "customMode": { "type": "boolean", "default": false, "description": "Enables style/title/lyrics control." },
          "style": { "type": "string", "description": "Style/genre tags. Required when `customMode` is true." },
          "title": { "type": "string", "description": "Track title. Required when `customMode` is true." },
          "instrumental": { "type": "boolean", "default": false, "description": "Generate without vocals." },
          "negativeTags": { "type": "string", "description": "Comma-separated tags to steer away from." },
          "callBackUrl": {
            "type": "string",
            "description": "Optional URL receiving one POST of the final record-info shape. Omit to use polling only."
          }
        }
      },
      "CoverRequest": {
        "type": "object",
        "required": ["audioId", "model"],
        "properties": {
          "audioId": {
            "type": "string",
            "description": "Source clip `id` from `response.sunoData[]`. Its recorded owner account is used for this operation.",
            "example": "95363029-a760-4a9a-98e7-33dbadc07be0"
          },
          "model": {
            "type": "string",
            "description": "Model `key` from `GET /api/v1/models`.",
            "example": "chirp-fenix"
          },
          "startS": {
            "type": "number",
            "description": "Window start in seconds. Must be supplied together with `endS`.",
            "example": 12
          },
          "endS": {
            "type": "number",
            "description": "Window end in seconds. Must be strictly after `startS`.",
            "example": 40
          },
          "prompt": {
            "type": "string",
            "description": "Lyrics for the new version. Required when `customMode` is true."
          },
          "customMode": { "type": "boolean", "default": false, "description": "Enables style/title/lyrics control." },
          "style": { "type": "string", "description": "Style/genre tags. Required when `customMode` is true." },
          "title": { "type": "string", "description": "Track title. Required when `customMode` is true." },
          "instrumental": { "type": "boolean", "default": false, "description": "Generate without vocals." },
          "negativeTags": { "type": "string", "description": "Comma-separated tags to steer away from." },
          "callBackUrl": {
            "type": "string",
            "description": "Optional URL receiving one POST of the final record-info shape. Omit to use polling only."
          }
        }
      },
      "LyricsRequest": {
        "type": "object",
        "required": ["prompt"],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Description of theme, mood and structure for the lyrics.",
            "example": "bittersweet summer song about a coastal town"
          },
          "callBackUrl": {
            "type": "string",
            "description": "Optional URL receiving the final result once. Omit to poll instead."
          }
        }
      },
      "SubmitEnvelope": {
        "type": "object",
        "description": "Standard `{code, msg, data}` envelope; `data.taskId` identifies the accepted task.",
        "properties": {
          "code": { "type": "integer", "const": 200 },
          "msg": { "type": "string", "const": "success" },
          "data": {
            "type": "object",
            "properties": {
              "taskId": {
                "type": "string",
                "description": "Server-generated task id. Poll it via record-info or receive it by callback.",
                "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
              }
            }
          }
        }
      },
      "RecordInfoEnvelope": {
        "type": "object",
        "description": "Standard `{code, msg, data}` envelope; `data` is the task state.",
        "properties": {
          "code": { "type": "integer", "const": 200 },
          "msg": { "type": "string", "const": "success" },
          "data": { "$ref": "#/components/schemas/RecordInfo" }
        }
      },
      "RecordInfo": {
        "type": "object",
        "description": "The task state. The same shape is used by record-info polling and completion callbacks.",
        "properties": {
          "taskId": { "type": "string", "description": "The task id." },
          "status": {
            "type": "string",
            "enum": ["PENDING", "GENERATING", "SUCCESS", "TEXT_SUCCESS", "GENERATE_AUDIO_FAILED", "CREATE_TASK_FAILED"],
            "description": "Terminal music statuses are SUCCESS and GENERATE_AUDIO_FAILED; CREATE_TASK_FAILED covers submission errors and ambiguous submissions. Lyrics end in TEXT_SUCCESS or CREATE_TASK_FAILED."
          },
          "type": {
            "type": "string",
            "enum": ["GENERATE", "EXTEND", "COVER", "LYRICS"],
            "description": "The kind of work this task performs."
          },
          "param": {
            "type": "string",
            "description": "The submitted input, rendered as a camelCase JSON string."
          },
          "response": {
            "type": "object",
            "description": "The result. Music tasks carry `sunoData[]` (empty until SUCCESS); lyrics tasks carry `text`.",
            "properties": {
              "taskId": { "type": "string" },
              "sunoData": {
                "type": "array",
                "description": "Tracks. Empty until the status is SUCCESS.",
                "items": { "$ref": "#/components/schemas/SunoTrack" }
              },
              "text": {
                "type": "string",
                "description": "The generated lyrics. Present for lyrics tasks instead of sunoData."
              }
            }
          },
          "errorCode": {
            "type": ["string", "null"],
            "description": "Set only on failed tasks; otherwise null."
          },
          "errorMessage": {
            "type": ["string", "null"],
            "description": "Set only on failed tasks; otherwise null."
          }
        }
      },
      "SunoTrack": {
        "type": "object",
        "description": "One generated track.",
        "properties": {
          "id": { "type": "string", "description": "Clip id — use as `audioId` for extend/cover." },
          "title": { "type": "string", "description": "Track title; may be empty for simple generations." },
          "audioUrl": { "type": "string", "description": "Direct MP3 download URL." },
          "streamAudioUrl": { "type": "string", "description": "Streaming URL (same MP3 for this gateway)." },
          "imageUrl": { "type": "string", "description": "Cover image URL." },
          "modelName": { "type": "string", "description": "Upstream model that rendered the track, e.g. `chirp-crow`." }
        }
      },
      "ModelsEnvelope": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "const": 200 },
          "msg": { "type": "string", "const": "success" },
          "data": {
            "type": "object",
            "properties": {
              "models": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/Model" }
              }
            }
          }
        }
      },
      "Model": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The value to pass as `model` on submit endpoints. Custom per-account models appear as `chirp-custom:<uuid>`.",
            "example": "chirp-crow"
          },
          "label": { "type": "string", "description": "Human-readable model name.", "example": "v5" },
          "accountCount": { "type": "integer", "description": "Number of usable accounts backing this model.", "example": 1 }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "Standard `{code, msg}` envelope; `code` mirrors the HTTP status and `data` is omitted.",
        "properties": {
          "code": { "type": "integer" },
          "msg": { "type": "string" }
        }
      }
    }
  }
}
