{
  "openapi": "3.1.0",
  "info": {
    "title": "Suno Gateway REST API",
    "version": "v1",
    "description": "Submit asynchronous media work through a multi-account gateway. It generates images, video and music across four upstream providers, and owns account selection, upstream credits, per-tier concurrency and routing. Your service owns customer logic.\n\n## Start here\n\nOne submit per medium, across every provider that can serve it. You name a model as `provider/model`; the gateway picks the account.\n\n| | submit | providers |\n|---|---|---|\n| images | `POST /api/v1/images/jobs` | ChatGPT, Google Flow, Grok Imagine |\n| video | `POST /api/v1/videos/jobs` | Google Flow, Grok Imagine |\n| music | `POST /api/v1/music/jobs` | Suno |\n\nEach returns `{task_id, status}` immediately. Read it back with **one** route, whatever made it:\n\n```\nGET /api/v1/jobs/{taskId}\n```\n\n`GET /api/v1/images/models`, `GET /api/v1/videos/models` and `GET /api/v1/music/models` publish what each model can honour — ratios, lengths, resolutions, whether it takes a reference image — and the validation reads the same table, so what is advertised and what is accepted cannot drift apart.\n\nAn input a model cannot honour is **refused, naming what it would have taken instead**, rather than accepted and quietly ignored. Send `drop_unsupported: true` to invert that: the field is removed, the generation runs, and every removal comes back in `dropped`.\n\n### A whole request\n\n```bash\ncurl -X POST https://<gateway>/api/v1/images/jobs \\\n  -H \"Authorization: Bearer $SERVICE_API_KEY\" \\\n  -H 'content-type: application/json' \\\n  -d '{\"prompt\":\"a brass diving helmet on wet stone\",\"model\":\"grok/grok-imagine\",\"aspect_ratio\":\"1:1\"}'\n\n# {\"task_id\":\"67720ee6-…\",\"status\":\"pending\",\"model\":\"grok/grok-imagine\"}\n\ncurl https://<gateway>/api/v1/jobs/67720ee6-… -H \"Authorization: Bearer $SERVICE_API_KEY\"\n\n# {\"task_id\":\"67720ee6-…\",\"medium\":\"image\",\"status\":\"succeeded\",\n#  \"images\":[{\"url\":\"https://<gateway>/api/v1/grok/file/67720ee6-…\"}]}\n```\n\n## The other two sections\n\n**Provider-native generation** — use it when you need a field the endpoints above do not have: `count`, `seed`, more than one reference image, `nsfw`, `watermark`, `skipUpsampler`, or an OpenAI-compatible endpoint for an existing SDK. Jobs created there are still readable with `GET /api/v1/jobs/{taskId}`.\n\n**Suno operations** — music work beyond generating a track: extend or cover audio, stems, MIDI, WAV and MP4 renders, personas, lyrics on their own, mashups, section replacement.\n\n## Authentication\n\nEvery request carries 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 sees only its own — an unknown id and another service's id both answer `404`, deliberately, so an id cannot be probed for what it is.\n\nThe file routes are the exception. `/api/v1/media/{clipId}`, `/api/v1/images/file/{taskId}`, `/api/v1/flow/file/{taskId}` and `/api/v1/grok/file/{taskId}` are unauthenticated and addressed by an unguessable id, because an `<img>`, `<video>` or `<audio>` src cannot attach a bearer token.\n\n## Two response shapes, and which is which\n\n**The endpoints under Generation return bare JSON**, snake_case, with no envelope — errors included, so one decoder reads every response:\n\n```json\n{\"task_id\": \"…\", \"medium\": \"image\", \"status\": \"pending\", \"images\": [], \"error_message\": \"\"}\n```\n\nStatus is `pending`, `succeeded` or `failed`. Nothing else.\n\n**The provider-native and Suno routes keep the `{code, msg, data}` envelope** they have always published, and their own longer status vocabulary:\n\n| Status | Terminal | Meaning |\n|---|---|---|\n| `PENDING` | no | Queued, routing, or being submitted. |\n| `TEXT_SUCCESS` | no | Music: clip ids exist; audio not yet streamable. |\n| `FIRST_SUCCESS` | no | Music: the first track is streamable. |\n| `SUCCESS` | yes | Complete. |\n| `GENERATE_AUDIO_FAILED` | yes | Music task failed; see `errorCode` / `errorMessage`. |\n| `GENERATE_LYRICS_FAILED` | yes | Lyrics task failed. |\n| `GENERATE_IMAGE_FAILED` | yes | ChatGPT image task failed. |\n| `GENERATE_FAILED` | yes | Google Flow or Grok Imagine task failed. |\n| `SENSITIVE_WORD_ERROR` | yes | Lyrics rejected for prohibited words. |\n| `CREATE_TASK_FAILED` | yes | Submission failed, or its outcome is unknown. |\n\nBoth are the same durable job underneath; they differ only in how they are spelled to the caller.\n\n## How long to wait\n\nPoll until a terminal status, then stop. Music takes 1–3 minutes. A Flow image is typically 30–60 seconds and a Flow video several minutes, both depending on upstream queue depth. Grok is faster: an image is a few seconds, a video around thirty.\n\n## Callbacks\n\nOptional on every endpoint — omitting one simply means you poll instead. Each delivery has a 15-second timeout and up to three attempts, so polling by task id remains the reliable result path.\n\nMusic callbacks use the stages `text`, `first` and `complete`, or `error` on failure. Lyrics, ChatGPT image, Google Flow and Grok Imagine callbacks each send one `complete` or `error` payload.\n\nThe field name differs by provider on the native routes: Suno and ChatGPT read `callBackUrl`, while Google Flow and Grok Imagine read `replyUrl` — the name useapi.net publishes for that surface."
  },
  "servers": [
    {
      "url": "/",
      "description": "This gateway (same origin as the docs)"
    }
  ],
  "security": [
    {
      "ServiceBearer": []
    }
  ],
  "tags": [
    {
      "name": "Generation (any provider)",
      "description": "Generate images, video and music. One endpoint per medium, one response shape, and one poll that works for all three.\n\nPick a model with `GET /api/v1/{images,videos,music}/models`, submit, then poll `GET /api/v1/jobs/{taskId}` until it is `succeeded` or `failed`.\n\nUse this unless you need something in the sections below."
    },
    {
      "name": "Provider-native generation",
      "description": "The same generations through each provider's own API shape. Use these when you need a field the endpoints above do not have: `count`, `seed`, more than one reference image, `nsfw`, `watermark`, `skipUpsampler`, or an OpenAI-compatible endpoint you can point an existing SDK at.\n\nResponses use the `{code, msg, data}` envelope. Jobs created here can still be read with `GET /api/v1/jobs/{taskId}`.\n\nThis section also holds the file routes that serve generated bytes."
    },
    {
      "name": "Suno operations",
      "description": "Music work beyond generating a track: extend or cover audio, separate stems, render MIDI, WAV or MP4, create personas, generate lyrics on their own, build mashups, replace a section — plus the routes that read each of them back.\n\nSuno only. Responses use the `{code, msg, data}` envelope."
    }
  ],
  "paths": {
    "/api/v1/generate": {
      "post": {
        "tags": [
          "Provider-native 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- `customMode`, `instrumental` and `model` are required.\n- `prompt` is required except for `customMode: true` + `instrumental: true`.\n- `customMode: true` requires non-empty `style` **and** `title`; `prompt`, when present, 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",
                    "customMode": false,
                    "instrumental": false,
                    "model": "AUTO",
                    "callBackUrl": "https://my-service.example.com/suno/callback"
                  }
                },
                "custom": {
                  "summary": "Custom mode with style and lyrics",
                  "value": {
                    "customMode": true,
                    "instrumental": false,
                    "title": "Coastline",
                    "style": "dreamy synth-pop, female vocal",
                    "prompt": "[Verse 1]\nSalt in the air...",
                    "model": "AUTO",
                    "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": "Music generation stage",
                "description": "POSTed for `text`, `first`, and `complete`, or `error` on failure. Each request has a 15-second timeout and up to three attempts.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/sounds": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "generateSounds",
        "summary": "Generate a sound effect or musical loop",
        "description": "Submit a Suno Sounds task. The captured upstream request uses `task: sound`, maps public model `V5` and sends loop, BPM, and key controls in `metadata.sound_configs`. It generates two short clips and uses the normal music polling/callback lifecycle. `grabLyrics` is accepted as a best-effort post-generation enrichment; it produces data only when the completed clip exposes lyrics suitable for the captured v3 alignment API and never fails otherwise successful audio.\n\nValidation:\n\n- `prompt` and `model` are required; prompt is limited to 500 characters.\n- Sounds support `V6` (the retired `V5` name is still accepted).\n- `soundTempo`, when present, must be an integer from 1 through 300.\n- `soundKey` defaults to `Any`.\n- `soundLoop` and `grabLyrics` default to false.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateSoundsRequest"
              },
              "examples": {
                "oneShot": {
                  "value": {
                    "prompt": "a short cinematic thunderclap with distant rain",
                    "model": "AUTO"
                  }
                },
                "loop": {
                  "value": {
                    "prompt": "warm analog bass loop",
                    "model": "AUTO",
                    "soundLoop": true,
                    "soundTempo": 120,
                    "soundKey": "Cm"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Sound generation stage",
                "description": "Same staged `text` / `first` / `complete` / `error` contract as music generation.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/add-instrumental": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "addInstrumental",
        "summary": "Add an instrumental arrangement to uploaded audio",
        "description": "Temporarily deferred. The upstream underpainting request requires a non-empty prompt and lyrics-project relationship, but the public sunoapi.org request does not provide the corresponding prompt. This gateway returns 501 rather than inventing that required value.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddInstrumentalRequest"
              },
              "example": {
                "uploadUrl": "https://cdn.example.com/vocals.mp3",
                "title": "Night Drive",
                "tags": "deep house, atmospheric synths",
                "negativeTags": "metal",
                "callBackUrl": "https://my-service.example.com/suno/callback",
                "model": "V4_5PLUS"
              }
            }
          }
        },
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "501": {
            "description": "Deferred until the required upstream prompt mapping is verified.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/add-vocals": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "addVocals",
        "summary": "Add vocals to uploaded audio",
        "description": "Downloads `uploadUrl`, completes Suno's captured metadata, description-acceptance, and lyrics-project preparation, then submits `task: overpainting` with `metadata.is_remix: true`. `prompt` is sent as the custom-mode vocal prompt and `style` as tags. `model` defaults to `V4_5PLUS`. `vocalGender`, `styleWeight`, `weirdnessConstraint`, and `audioWeight` are accepted but currently ignored until their upstream representation is captured.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddVocalsRequest"
              },
              "example": {
                "uploadUrl": "https://cdn.example.com/instrumental.mp3",
                "prompt": "[Verse] Sing softly over the midnight rhythm",
                "title": "Night Drive",
                "style": "deep house, intimate female vocal",
                "negativeTags": "metal",
                "callBackUrl": "https://my-service.example.com/suno/callback",
                "model": "V4_5PLUS"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Add-vocals generation stage",
                "description": "Staged `text`, `first`, `complete`, or `error` music callback.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/upload-cover": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "uploadCover",
        "summary": "Cover uploaded audio",
        "description": "Downloads `uploadUrl`, completes the captured metadata, description-acceptance, and lyrics-project preparation, then submits `task: cover`. Custom vocal mode sends exact lyrics/style/title and the prepared lyrics-project ID. Custom instrumental mode omits that ID. Non-custom mode uses the captured `SIMPLE_REMIX` payload with source lyrics in `prompt`, the caller description in `gpt_description_prompt`, and `override_fields: [\"prompt\"]`. Persona, weight, vocal-gender, and duration controls are accepted but currently ignored until captured.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadCoverRequest"
              },
              "examples": {
                "customVocal": {
                  "value": {
                    "uploadUrl": "https://cdn.example.com/source.mp3",
                    "prompt": "[Verse] Exact lyrics",
                    "style": "jazz",
                    "title": "Midnight Cover",
                    "customMode": true,
                    "instrumental": false,
                    "model": "AUTO",
                    "callBackUrl": "https://my-service.example.com/suno/callback"
                  }
                },
                "simple": {
                  "value": {
                    "uploadUrl": "https://cdn.example.com/source.mp3",
                    "prompt": "Turn this into a relaxed jazz arrangement",
                    "customMode": false,
                    "instrumental": false,
                    "model": "AUTO",
                    "callBackUrl": "https://my-service.example.com/suno/callback"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Upload-cover generation stage",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/upload-extend": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "uploadExtend",
        "summary": "Extend uploaded audio",
        "description": "Downloads and fully prepares `uploadUrl`, then submits the captured custom `task: upload_extend` flow with `continue_clip_id`, `metadata.is_remix`, `metadata.lyrics_updated`, and the prepared lyrics-project ID. `defaultParamFlag: true` requires style, title, and a positive `continueAt`. `defaultParamFlag: false` is temporarily rejected because it means inherit source parameters, not Simple mode, and its exact continuation policy is not yet verified. Persona, vocal-gender, and weight controls are accepted but currently ignored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadExtendRequest"
              },
              "examples": {
                "custom": {
                  "value": {
                    "uploadUrl": "https://cdn.example.com/source.mp3",
                    "defaultParamFlag": true,
                    "prompt": "[Verse] Continue these lyrics",
                    "style": "jazz",
                    "title": "Longer Night",
                    "continueAt": 25,
                    "model": "AUTO",
                    "callBackUrl": "https://my-service.example.com/suno/callback"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Upload-extend generation stage",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/extend": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "extendMusic",
        "summary": "Extend a clip previously created through this gateway",
        "description": "Continue a clip created through this gateway. The source account is reused automatically.\n\nParameter modes:\n\n- `defaultParamFlag: false` inherits the source prompt, style, title, instrumental setting, negative tags, and extends from its recorded duration. Submit it only after the source task reaches `SUCCESS`; `FIRST_SUCCESS` means audio is streamable but its final duration may not be available yet.\n- `defaultParamFlag: true` uses custom parameters and requires `continueAt`, `style`, and `title`; `prompt` is required for vocals and must be omitted for instrumentals.\n- `audioId`, `model`, and `defaultParamFlag` are required. The model must match the source clip.\n- `callBackUrl` is optional.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtendRequest"
              },
              "examples": {
                "inheritSource": {
                  "summary": "Inherit source parameters and continue from its duration",
                  "value": {
                    "defaultParamFlag": false,
                    "audioId": "95363029-a760-4a9a-98e7-33dbadc07be0",
                    "model": "AUTO"
                  }
                },
                "custom": {
                  "summary": "Continue from second 30.5 with custom parameters",
                  "value": {
                    "defaultParamFlag": true,
                    "instrumental": false,
                    "audioId": "95363029-a760-4a9a-98e7-33dbadc07be0",
                    "model": "AUTO",
                    "continueAt": 30.5,
                    "prompt": "[Verse]\nContinue the story...",
                    "style": "dreamy synth-pop",
                    "title": "Coastline Continued"
                  }
                }
              }
            }
          }
        },
        "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": "Music extension stage",
                "description": "Same staged `text` / `first` / `complete` / `error` contract as music generation.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/lyrics": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "generateLyrics",
        "summary": "Generate lyrics without submitting a music generation",
        "description": "Generate lyrics only—no music submission. The prompt is required and limited to 200 characters. `callBackUrl` is optional; omit it to poll with `GET /api/v1/lyrics/record-info`.",
        "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": "Lyrics completion or failure",
                "description": "POSTed once as `complete`, or `error` on failure. Each request has a 15-second timeout and up to three attempts.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/LyricsCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/record-info": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "musicRecordInfo",
        "summary": "Poll a music task by taskId",
        "description": "Fetch the current state of a music generation or extension task. 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://gateway.example.com/api/v1/media/95363029-a760-4a9a-98e7-33dbadc07be0",
                              "streamAudioUrl": "https://gateway.example.com/api/v1/media/95363029-a760-4a9a-98e7-33dbadc07be0",
                              "imageUrl": "https://cdn2.suno.ai/image_95363029-a760-4a9a-98e7-33dbadc07be0.jpeg",
                              "modelName": "V6"
                            },
                            {
                              "id": "858af1e2-e288-45a6-846c-24f1409a432e",
                              "title": "",
                              "audioUrl": "https://gateway.example.com/api/v1/media/858af1e2-e288-45a6-846c-24f1409a432e",
                              "streamAudioUrl": "https://gateway.example.com/api/v1/media/858af1e2-e288-45a6-846c-24f1409a432e",
                              "imageUrl": "https://cdn2.suno.ai/image_858af1e2-e288-45a6-846c-24f1409a432e.jpeg",
                              "modelName": "V6"
                            }
                          ]
                        },
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  },
                  "pending": {
                    "summary": "Still running",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "status": "TEXT_SUCCESS",
                        "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": [
          "Suno operations"
        ],
        "operationId": "lyricsRecordInfo",
        "summary": "Poll a lyrics task by taskId",
        "description": "Fetch the current state of a lyrics task. `response.data` is empty while no result exists and contains the generated lyrics variant on success. The current upstream integration produces one variant and does not provide a title, so `title` is an empty string.",
        "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": "SUCCESS",
                        "type": "LYRICS",
                        "param": "{\"prompt\":\"bittersweet summer song about a coastal town\"}",
                        "response": {
                          "taskId": "3f8c1d02-6a94-47be-9e51-c40b7f2d8a13",
                          "data": [
                            {
                              "text": "[Verse 1]\nSalt in the air...",
                              "title": "",
                              "status": "complete",
                              "errorMessage": ""
                            }
                          ]
                        },
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/generate/get-timestamped-lyrics": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getTimestampedLyrics",
        "summary": "Get word-level lyric timings for a generated track",
        "description": "Read timestamped lyrics for a clip created by the specified task. The task and clip must belong to the calling service. The gateway sends the captured `POST /aligned_lyrics/v3` request with the clip's exact lyrics and polls the same endpoint when state is `running`; instrumental tracks return empty arrays without an upstream call. Suno v3 does not return the public flat `waveformData`, `hootCer`, or `isStreamed` fields, so those currently remain `[]`, `0`, and `false` respectively until that adapter mapping is verified.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimestampedLyricsRequest"
              },
              "example": {
                "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                "audioId": "e2319d75-3dc5-43a5-9c2e-99498cadc7dc"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Timestamped lyrics, or an empty word list for an instrumental track.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimestampedLyricsEnvelope"
                },
                "example": {
                  "code": 200,
                  "msg": "success",
                  "data": {
                    "alignedWords": [
                      {
                        "word": "Morning ",
                        "success": true,
                        "startS": 1.58,
                        "endS": 3.16,
                        "palign": 0.99
                      }
                    ],
                    "waveformData": [
                      0.08,
                      0.06
                    ],
                    "hootCer": 0.12,
                    "isStreamed": true
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "description": "The owning upstream account could not return lyric timings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/vocal-removal/generate": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "separateVocalsFromMusic",
        "summary": "Separate vocals or instrument stems",
        "description": "Submits Suno’s captured `task: gen_stem` flow. `separate_vocal` extracts Lead Vocal, `split_stem` uses the captured Twelve mode, and `split_stem_advanced` forwards the selected `stemName`. Existing gateway clips retain account affinity; `audioUrl` is downloaded with SSRF protection, limited to 20 MB, and initialized through the shared upload pipeline. Stem extraction reserves 50 upstream credits.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VocalRemovalGenerateRequest"
              },
              "examples": {
                "separateExisting": {
                  "value": {
                    "taskId": "source-task-id",
                    "audioId": "source-clip-id",
                    "type": "separate_vocal",
                    "callBackUrl": "https://example.com/callback"
                  }
                },
                "advancedUpload": {
                  "value": {
                    "audioUrl": "https://cdn.example.com/source.mp3",
                    "type": "split_stem_advanced",
                    "stemName": "Bass",
                    "callBackUrl": "https://example.com/callback"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Source task or audio not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "vocalRemovalGenerated": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Vocal removal completion",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/VocalRemovalCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/vocal-removal/record-info": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getVocalSeparationDetails",
        "summary": "Get vocal separation details",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Task ID returned by vocal-removal/generate."
          }
        ],
        "responses": {
          "200": {
            "description": "Task state and separated tracks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VocalRemovalRecordInfoEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Task not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/midi/generate": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "generateMidi",
        "summary": "Generate MIDI from separated audio",
        "description": "Starts MIDI extraction by polling Suno’s captured `GET /api/gen/{stemId}/midi` flow. The source vocal-removal task must be complete. `audioId` selects one originData track; when omitted, all separated tracks are processed and their instrument arrays are merged in source order.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MidiGenerateRequest"
              },
              "example": {
                "taskId": "vocal-removal-task-id",
                "audioId": "separated-track-id",
                "callBackUrl": "https://example.com/callback"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Vocal-removal task not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "midiGenerated": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "MIDI completion",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MidiCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/midi/record-info": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getMidiDetails",
        "summary": "Get MIDI generation details",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "MIDI task state and note data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MidiRecordInfoEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Task not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/suno/cover/generate": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "generateCoverArt",
        "summary": "Generate music cover artwork",
        "description": "Temporarily deferred. The public request provides only `taskId`, while the captured upstream image request requires a prompt and Suno's live modal starts with an empty prompt. The gateway returns 501 rather than deriving or inventing a prompt. Record-info remains available for previously persisted cover-art tasks.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CoverArtGenerateRequest"
              },
              "example": {
                "taskId": "completed-music-task-id",
                "callBackUrl": "https://example.com/callback"
              }
            }
          }
        },
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "501": {
            "description": "Deferred until the required upstream prompt mapping is verified.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/suno/cover/record-info": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getCoverArtDetails",
        "summary": "Get cover artwork generation details",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Cover task ID returned by suno/cover/generate."
          }
        ],
        "responses": {
          "200": {
            "description": "Cover task state and generated image URLs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CoverArtRecordInfoEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Task not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/mashup": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "generateMashup",
        "summary": "Generate a music mashup",
        "description": "Downloads exactly two public source audio URLs with SSRF protection and a 100 MiB limit per source, fully prepares both through one selected Suno account, then submits the captured `task: mashup_condition` flow. Custom vocal and custom instrumental V5 are verified. Non-custom and non-V5 variants are temporarily rejected. Duration, vocal-gender, and weight controls are accepted but currently ignored until captured.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MashupGenerateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "mashupGenerated": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Mashup generation progress or completion",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/generate/generate-persona": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "generatePersona",
        "summary": "Create a persona from a generated clip",
        "description": "Synchronous: creates a Suno persona (POST /api/persona/create/) from the source clip's vocals and returns {personaId, name} directly. No job and no callback; not pollable.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GeneratePersonaRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "taskId": "b3c2c9f1-3c2c-4f1a-9f2b-1234567890ab",
                    "audioId": "a1b2c3d4-1111-2222-3333-444455556666",
                    "name": "Midnight Voice",
                    "description": "Soft female vocals"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Persona created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonaEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/generate/replace-section": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "replaceSection",
        "summary": "Replace a section of a song (infill)",
        "description": "Two reference modes: (1) taskId + audioId replaces a section of an existing gateway clip; (2) uploadUrl + model replaces a section of uploaded audio. Runs the captured v2-web infill payload with context lyrics derived from aligned lyrics when available. The replaced window must be at least 10 seconds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplaceSectionRequest"
              },
              "examples": {
                "existingClip": {
                  "value": {
                    "taskId": "b3c2c9f1-3c2c-4f1a-9f2b-1234567890ab",
                    "audioId": "a1b2c3d4-1111-2222-3333-444455556666",
                    "prompt": "[Verse] New section lyrics",
                    "tags": "jazz, lo-fi",
                    "title": "Midnight Drive (reworked)",
                    "negativeTags": "metal",
                    "infillStartS": 45,
                    "infillEndS": 70,
                    "fullLyrics": "[Verse 1] old words\\n[Verse 2] more words"
                  }
                },
                "uploadedAudio": {
                  "value": {
                    "uploadUrl": "https://cdn.example.com/source.mp3",
                    "model": "AUTO",
                    "prompt": "[Verse] New section lyrics",
                    "tags": "jazz",
                    "title": "Reworked",
                    "infillStartS": 10,
                    "infillEndS": 30,
                    "fullLyrics": "old lyrics"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "taskCompleted": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Replace-section completion callback",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/MusicCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/models": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "listModels",
        "summary": "List models currently backed by at least one usable account",
        "description": "Use `key` as the `model` value on submit endpoints. Standard models are returned as public aliases; unknown or custom models retain their raw key.",
        "responses": {
          "200": {
            "description": "Available models.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelsEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "models": [
                          {
                            "key": "AUTO",
                            "label": "account default",
                            "accountCount": 9
                          },
                          {
                            "key": "V6",
                            "label": "v6",
                            "accountCount": 7
                          },
                          {
                            "key": "V6_WILD",
                            "label": "v6-wild",
                            "accountCount": 7
                          },
                          {
                            "key": "V6_MINI",
                            "label": "v6-mini",
                            "accountCount": 9
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/wav/generate": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "convertToWav",
        "summary": "Convert a generated track to WAV format",
        "description": "Submit a WAV export for a clip previously created through this gateway. The source `taskId` and `audioId` must belong to the calling service, and the audio must have been created by that task. `callBackUrl` is required and receives a single completion notification carrying `audioWavUrl`.\n\nUpstream mapping: `POST /api/gen/{clipId}/convert_wav/` (HTTP 204), then the signed URL is read from `GET /api/gen/{clipId}/wav_file/`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WavGenerateRequest"
              },
              "example": {
                "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                "audioId": "e2319d75-3dc5-43a5-9c2e-99498cadc7dc",
                "callBackUrl": "https://my-service.example.com/suno/wav-callback"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversion accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "callbacks": {
          "wavGenerated": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "WAV conversion completion",
                "description": "Sent once when the export completes, carrying the signed WAV download URL.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/WavCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/wav/record-info": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getWavConversionDetails",
        "summary": "Poll a WAV conversion task",
        "description": "Fetch the state of a WAV conversion created by `POST /api/v1/wav/generate`. The task must belong to the calling service.",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "description": "Task id returned by the WAV generate endpoint.",
            "schema": {
              "type": "string"
            },
            "example": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c"
          }
        ],
        "responses": {
          "200": {
            "description": "WAV conversion state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WavRecordInfoEnvelope"
                },
                "example": {
                  "code": 200,
                  "msg": "success",
                  "data": {
                    "taskId": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c",
                    "musicId": "8551c8ac-662c-4a0f-b1c4-2e8f9d1a3b7c",
                    "musicIndex": 0,
                    "callbackUrl": "https://api.example.com/callback",
                    "completeTime": "2025-01-01 00:10:00",
                    "response": {
                      "audioWavUrl": "https://example.com/s/04e6c8ace727.wav"
                    },
                    "successFlag": "SUCCESS",
                    "createTime": "2025-01-01 00:00:00",
                    "errorCode": null,
                    "errorMessage": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/mp4/generate": {
      "post": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "createMusicVideo",
        "summary": "Create a music video for a generated track",
        "description": "Submit a music-video render for a clip previously created through this gateway. The source `taskId` and `audioId` must belong to the calling service, and the audio must have been created by that task. `callBackUrl` is required and receives a single completion notification carrying `video_url`.\n\nUpstream mapping: `POST /api/video/generate/{clipId}/` with an empty body (HTTP 204), then `GET /api/video/generate/{clipId}/status/` returns `{\"status\":\"processing\"}` until it returns `{\"status\":\"complete\",\"video_url\":\"...\"}`. `author` and `domainName` are accepted for reference compatibility but are not forwarded upstream (the captured create request has an empty body).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Mp4GenerateRequest"
              },
              "example": {
                "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                "audioId": "e2319d75-3dc5-43a5-9c2e-99498cadc7dc",
                "callBackUrl": "https://my-service.example.com/suno/mp4-callback"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Render accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "callbacks": {
          "mp4Generated": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Music video completion",
                "description": "Sent once when the render completes, carrying the signed MP4 URL.",
                "requestBody": {
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/Mp4CallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback acknowledged."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/mp4/record-info": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getMusicVideoDetails",
        "summary": "Poll a music-video render task",
        "description": "Fetch the state of a music-video render created by `POST /api/v1/mp4/generate`. The task must belong to the calling service.",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "description": "Task id returned by the MP4 generate endpoint.",
            "schema": {
              "type": "string"
            },
            "example": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c"
          }
        ],
        "responses": {
          "200": {
            "description": "Music-video render state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Mp4RecordInfoEnvelope"
                },
                "example": {
                  "code": 200,
                  "msg": "success",
                  "data": {
                    "taskId": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c",
                    "musicId": "8551c8ac-662c-4a0f-b1c4-2e8f9d1a3b7c",
                    "musicIndex": 0,
                    "callbackUrl": "https://api.example.com/callback",
                    "completeTime": "2025-01-01 00:10:00",
                    "response": {
                      "videoUrl": "https://example.com/videos/video_847715e66259.mp4"
                    },
                    "successFlag": "SUCCESS",
                    "createTime": "2025-01-01 00:00:00",
                    "errorCode": null,
                    "errorMessage": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/media/{clipId}": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getClipMedia",
        "summary": "Stream a generated clip's audio as MP3",
        "description": "The audio origin behind every `audioUrl`/`streamAudioUrl`. Suno stopped serving clip audio in the clear, so this route fetches the encrypted rendition through the account that generated the clip, decrypts it and streams the bytes.\n\nUnauthenticated by design: media players cannot send a bearer token, and the clip id is the only secret — clips this gateway did not create return 404.\n\nThe default rendition is **MP3** (`audio/mpeg`, 192 kbps CBR), transcoded live and never written to disk. It still carries an exact `Content-Length` and supports `Range`, because constant-bitrate MP3 has a predictable frame layout. The gateway keeps finished MP3 renditions in memory (`media.cache_size_mb`), so a clip is encoded once and every replay or seek is served from RAM; responses are `Cache-Control: public, max-age=3600` because a finished clip's audio is immutable.\n\n`?format=m4a` serves what Suno publishes instead: Opus in MP4 (`audio/mp4`, ~145 kbps), one lossy generation better and free of transcoding cost — use it when the player can read `audio/mp4`. A clip that is still rendering is delivered as chunked audio with no `Content-Length` and cannot be seeked.",
        "security": [],
        "parameters": [
          {
            "name": "clipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Clip id from `RecordInfo.response.sunoData[].id`."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "mp3",
                "m4a"
              ],
              "default": "mp3"
            },
            "description": "Omit (or `mp3`) for the transcoded MP3. `m4a` serves Suno's own Opus-in-MP4 rendition unchanged. An explicit `mp3` answers 501 when the gateway has no transcoder; the default falls back to `m4a` in that case."
          },
          {
            "name": "Range",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Single byte range, `bytes=<start>-` or `bytes=<start>-<end>`. Other forms are ignored and the whole clip is returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The whole clip. `audio/mpeg` by default, `audio/mp4` with format=m4a.",
            "headers": {
              "Accept-Ranges": {
                "description": "`bytes` when the rendition can be seeked.",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Disposition": {
                "description": "`inline` with a `<clipId>.<ext>` filename.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "audio/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/webm": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "The requested byte range.",
            "headers": {
              "Content-Range": {
                "description": "`bytes <start>-<end>/<total>`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "audio/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/webm": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 400,
                      "msg": "format must be mp3 or m4a"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No clip with this id was generated by this gateway.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 404,
                      "msg": "unknown clip"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The clip exists but has no playable audio yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 409,
                      "msg": "audio is not ready yet"
                    }
                  }
                }
              }
            }
          },
          "416": {
            "description": "The requested range lies outside the clip, or the clip is still rendering and cannot be seeked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 416,
                      "msg": "range is not satisfiable"
                    }
                  }
                }
              }
            }
          },
          "501": {
            "description": "`?format=mp3` was requested explicitly but this gateway has no transcoder. The default rendition falls back to m4a instead of failing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 501,
                      "msg": "mp3 delivery is not available on this gateway"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Upstream did not deliver the audio.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "default": {
                    "value": {
                      "code": 502,
                      "msg": "upstream media unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/generate": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "generateImage",
        "summary": "Generate an image",
        "description": "Submit an image generation to the ChatGPT provider. The prompt is the whole request: the upstream consumer surface exposes no size, quality or format controls, so `model` is accepted for client compatibility and ignored.\n\nReturns 503 when the gateway is deployed without `chatgpt.solver_path`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageGenerateRequest"
              },
              "example": {
                "prompt": "High quality AirPods Pro on a white background, photorealistic",
                "callBackUrl": "https://my-service.example.com/suno/image-callback"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "imageGenerated": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Image generation completed",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/ImageCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback accepted."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/record-info": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "getImageDetails",
        "summary": "Poll an image generation task",
        "description": "Fetch the state of an image generation created by `POST /api/v1/images/generate`. The task must belong to the calling service.",
        "parameters": [
          {
            "name": "taskId",
            "in": "query",
            "required": true,
            "description": "Task id returned by the image generate endpoint.",
            "schema": {
              "type": "string"
            },
            "example": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c"
          }
        ],
        "responses": {
          "200": {
            "description": "Image generation state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageRecordInfoEnvelope"
                },
                "example": {
                  "code": 200,
                  "msg": "success",
                  "data": {
                    "taskId": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c",
                    "status": "SUCCESS",
                    "param": "{\"prompt\":\"a red bicycle on a beach\"}",
                    "response": {
                      "taskId": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c",
                      "imageUrl": "https://gateway.example.com/api/v1/images/file/988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c",
                      "mimeType": "image/png",
                      "sizeBytes": 1841233
                    },
                    "errorCode": null,
                    "errorMessage": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/images/file/{taskId}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "downloadImage",
        "summary": "Download a generated image",
        "description": "Stream the generated image bytes. This route is deliberately unauthenticated and addressed by the unguessable task UUID, because an `<img src>` and an OpenAI SDK `url` consumer cannot attach a bearer token. Range and conditional requests are supported.\n\nFiles are retained for `chatgpt.image_retention` (default 7 days) after generation and then deleted; this route answers 404 afterwards while the task record stays readable. Download promptly or copy the bytes to your own storage.",
        "security": [],
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "Task id of a succeeded image generation.",
            "schema": {
              "type": "string"
            },
            "example": "988e5b84-c8d3-4a0f-b1c4-2e8f9d1a3b7c"
          }
        ],
        "responses": {
          "200": {
            "description": "Image bytes.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/images/generations": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "createImageGeneration",
        "summary": "Generate images (OpenAI-compatible, synchronous)",
        "description": "OpenAI Images-compatible endpoint over the same durable pipeline: point an existing OpenAI SDK at `base_url=<gateway>/api/v1`. The call blocks until every image is terminal, up to `chatgpt.direct_timeout`, then returns `b64_json` (default) or a gateway-hosted `url`.\n\nOn a 504 the jobs keep running and stay retrievable through `GET /api/v1/images/record-info`. Errors use OpenAI's envelope, not this gateway's `{code,msg}` envelope.\n\n`model`, `size`, `quality`, `background`, `output_format`, `output_compression`, `moderation`, `partial_images`, `style` and `user` are accepted for SDK compatibility and ignored: the ChatGPT consumer conversation exposes none of them. `stream` is rejected. `/v1/images/edits` and `/v1/images/variations` are not implemented.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenAIImageGenerationRequest"
              },
              "example": {
                "model": "gpt-image-1",
                "prompt": "a red bicycle on a beach, photorealistic",
                "n": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated images.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIImagesResponse"
                },
                "example": {
                  "created": 1767225600,
                  "data": [
                    {
                      "b64_json": "iVBORw0KGgoAAAANSUhEUg..."
                    }
                  ],
                  "output_format": "png"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Upstream rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Image generation is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Still running; poll the task API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{taskId}": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "getJob",
        "summary": "Get a job",
        "description": "Reads any job by id — image, video or music — so you do not need to remember which endpoint created it. `medium` tells you which result field to read: `images`, `videos` or `tracks`.\n\nWorks for jobs created by the provider-native routes too.\n\nPoll until `status` is `succeeded` or `failed`. A task belonging to another API key answers `404`, the same as an unknown id.",
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "404": {
            "description": "No such task for this caller, or a task from a provider-native route this surface does not publish.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/jobs": {
      "post": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "createImageJob",
        "summary": "Generate an image",
        "description": "Send a prompt and a model id. Returns a `task_id` immediately; the image is ready when the job reaches `succeeded`.\n\n`model` is `provider/model` — see `GET /api/v1/images/models` for the list and what each one accepts, or send `auto` to let the gateway pick a provider that can take the request now. Omit it for `grok/grok-imagine`.\n\nOptional fields a model cannot honour are **refused**, and the error names what that model takes instead. Send `drop_unsupported: true` to have them removed instead; the response then lists every field it dropped.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageJobRequest"
              },
              "example": {
                "prompt": "a floating market at dawn, cinematic",
                "model": "grok/grok-imagine-pro",
                "aspect_ratio": "16:9"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job submitted; poll by task_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — unknown model, a ratio or optional input the model does not honour, a prompt over the model's cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "500": {
            "description": "Encoding or persistence failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "503": {
            "description": "The resolved provider is not configured on this gateway.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/jobs/{taskId}": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "getImageJob",
        "summary": "Get an image job",
        "description": "Poll until `status` is `succeeded` or `failed`. On success, `images[]` holds one URL per image, already served by this gateway — fetch them directly, no bearer token needed.\n\n`GET /api/v1/jobs/{taskId}` does the same without needing to know the medium.",
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job's current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "404": {
            "description": "No such task for this caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "500": {
            "description": "Load failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/models": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "listImageModels",
        "summary": "List image models",
        "description": "Every image model you can name, with the aspect ratios it renders, its prompt limit, and which optional fields it accepts. `enabled: false` means this gateway cannot reach that provider right now.\n\nValidation reads this same table, so anything listed here is accepted.\n\n`auto` is published beside the rows: whether it can route anywhere, and the order providers are tried in.",
        "responses": {
          "200": {
            "description": "The capability table.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageModelsEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/videos/jobs": {
      "post": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "createVideoJob",
        "summary": "Generate a video",
        "description": "Send a prompt and a model id. Returns a `task_id` immediately; a clip takes from about thirty seconds to several minutes depending on the model.\n\n`model` is `provider/model` — see `GET /api/v1/videos/models`, or send `auto` to let the gateway pick a provider that can take the request now. Omit it for `grok/grok-imagine-video`.\n\nWhat a model accepts varies more than for images: clip lengths differ per model, only some let you choose a resolution, and `image_url` turns both Flow and Grok into image-to-video (the supplied image is the first frame; on Grok the gateway uploads it to the routed account first). A value the model does not offer is refused and the error names what it does offer; send `drop_unsupported: true` to have it removed and reported instead.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VideoJobRequest"
              },
              "example": {
                "prompt": "a red kite over a grey sea, handheld",
                "model": "flow/veo-3.1-fast",
                "duration": 6,
                "aspect_ratio": "landscape"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job submitted; poll by task_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — unknown model, a length or resolution the model does not offer, an image_url on a model that does not take one, a prompt over the cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          },
          "503": {
            "description": "The provider is not configured on this gateway, or the task persisted but could not be enqueued — poll by task_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/videos/jobs/{taskId}": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "getVideoJob",
        "summary": "Get a video job",
        "description": "Poll until `status` is `succeeded` or `failed`. On success, `videos[]` holds the clip URL, served by this gateway and fetchable without a bearer token.\n\n`GET /api/v1/jobs/{taskId}` does the same without needing to know the medium.",
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          },
          "404": {
            "description": "No such video task for this caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoJob"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/videos/models": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "listVideoModels",
        "summary": "List video models",
        "description": "Every video model you can name, with the aspect ratios, clip lengths and resolutions it offers, and whether it accepts a first frame. `enabled: false` means this gateway cannot reach that provider right now.\n\nValidation reads this same table, so anything listed here is accepted.\n\n`auto` is published beside the rows: whether it can route anywhere, and the order providers are tried in.",
        "responses": {
          "200": {
            "description": "The capability table.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoModelsEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/music/jobs": {
      "post": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "createMusicJob",
        "summary": "Generate music",
        "description": "Send a prompt. Returns a `task_id` immediately; a track takes one to three minutes, and one request produces two takes.\n\nBy default `prompt` describes the track you want. Add `style` and `title` and it becomes the lyrics instead — that pair is how you ask for a custom track, and both are required together.\n\nOmit `model` to let the gateway pick one it can currently serve; `GET /api/v1/music/models` lists them.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MusicJobRequest"
              },
              "example": {
                "prompt": "slow dub techno, tape hiss, distant foghorn",
                "instrumental": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job submitted; poll by task_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — unknown model, an empty prompt, half of custom mode, or a field this schema infers rather than accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          },
          "503": {
            "description": "The provider is not configured on this gateway, or the task persisted but could not be enqueued — poll by task_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/music/jobs/{taskId}": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "getMusicJob",
        "summary": "Get a music job",
        "description": "Poll until `status` is `succeeded` or `failed`. `tracks[]` fills in as takes finish, so a job still reading `pending` can already carry one playable track — poll again for the second.\n\n`GET /api/v1/jobs/{taskId}` does the same without needing to know the medium.",
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          },
          "404": {
            "description": "No such music task for this caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicJob"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/music/models": {
      "get": {
        "tags": [
          "Generation (any provider)"
        ],
        "operationId": "listMusicModels",
        "summary": "List music models",
        "description": "Music models the account pool can currently serve, with how many accounts back each one. `enabled: false` and `accounts: 0` mean the model exists but nothing can run it right now.\n\nAn empty list means no account is usable; the submit answers `503` rather than rejecting your request.",
        "responses": {
          "200": {
            "description": "The servable menu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MusicModelsEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/edit": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "editImage",
        "summary": "Edit an image",
        "description": "Submit a source image URL plus a prompt and the ChatGPT provider edits/transforms the image (style transfer, relighting, ...). The gateway fetches the URL, validates the bytes (jpeg/png/webp, at most 20 MiB, dimensions read from the bytes) and persists them before the task is created, so the stored task never depends on the caller URL staying alive.\n\nThere is no `ratio` parameter: upstream derives the output shape from the source image itself. An edit consumes the account's upload allowance in addition to the image allowance.\n\nReturns 503 when the gateway is deployed without `chatgpt.solver_path`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageEditRequest"
              },
              "example": {
                "prompt": "turn this image into a bright sunny day with blue sky and white clouds",
                "image_url": "https://my-cdn.example.com/photos/hoai.jpg",
                "callBackUrl": "https://my-service.example.com/suno/image-callback"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Edit accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "imageEdited": {
            "{$request.body#/callBackUrl}": {
              "post": {
                "summary": "Image edit completed",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/ImageCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback accepted."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/images/edits": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "editImageOpenAI",
        "summary": "Edit an image (OpenAI-compatible, synchronous)",
        "description": "OpenAI Images-compatible multipart edit over the same durable pipeline: upload the source image as a form `image` file plus a `prompt`. The call blocks until the edit is terminal, up to `chatgpt.direct_timeout`, then returns `b64_json` (default) or a gateway-hosted `url`.\n\nOn a 504 the job keeps running and stays retrievable through `GET /api/v1/images/record-info`. Errors use OpenAI's envelope, not this gateway's `{code,msg}` envelope.\n\n`n` must be 1: every copy would re-upload the same source image. `mask` is rejected. `model`, `size`, `quality`, `background`, `output_format`, `output_compression`, `moderation`, `partial_images`, `style` and `user` are accepted for SDK compatibility and ignored. The source must be jpeg, png or webp and at most 20 MiB; output shape follows the source image (no `size` control).",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image",
                  "prompt"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "The source image to edit: jpeg, png or webp, at most 20 MiB."
                  },
                  "prompt": {
                    "type": "string",
                    "maxLength": 32000,
                    "description": "What to change about the image."
                  },
                  "n": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1,
                    "default": 1,
                    "description": "Must be 1: every copy would re-upload the same source image."
                  },
                  "response_format": {
                    "type": "string",
                    "enum": [
                      "b64_json",
                      "url"
                    ],
                    "default": "b64_json"
                  },
                  "mask": {
                    "type": "string",
                    "format": "binary",
                    "description": "Rejected with 400: selective edits are not supported."
                  },
                  "model": {
                    "type": "string",
                    "description": "Accepted for OpenAI SDK compatibility; ignored."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Edited image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIImagesResponse"
                },
                "example": {
                  "created": 1767225600,
                  "data": [
                    {
                      "b64_json": "iVBORw0KGgoAAAANSUhEUg..."
                    }
                  ],
                  "output_format": "png"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid service token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Upstream rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Image editing is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Still running; poll the task API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flow/images": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "generateFlowImage",
        "summary": "Generate or edit an image",
        "description": "Submit an image generation to Google Flow. Supplying any `reference_N` makes it an edit.\n\nReturns 503 when the gateway is deployed with `ggflow.enabled: false` or without a `captcha.url` the solver can reach.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlowImageRequest"
              },
              "examples": {
                "textToImage": {
                  "summary": "From a prompt alone",
                  "value": {
                    "prompt": "A red bicycle leaning on a whitewashed wall, golden hour",
                    "model": "nano-banana-pro",
                    "aspectRatio": "landscape",
                    "count": 1
                  }
                },
                "edit": {
                  "summary": "Edit, with the shape taken from the reference",
                  "value": {
                    "prompt": "Make the bicycle blue and add a wicker basket",
                    "aspectRatio": "auto",
                    "reference_1": {
                      "url": "https://example.com/bicycle.jpg"
                    },
                    "replyUrl": "https://my-service.example.com/flow-callback"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "flowImageGenerated": {
            "{$request.body#/replyUrl}": {
              "post": {
                "summary": "Flow image generation finished",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/FlowCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback accepted."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flow/videos": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "generateFlowVideo",
        "summary": "Generate a video",
        "description": "Submit a video generation to Google Flow. One route serves four upstream operations; which one runs follows from the images supplied rather than from a `mode` field — see the request schema.\n\nReturns 503 when the gateway is deployed with `ggflow.enabled: false` or without a `captcha.url` the solver can reach.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlowVideoRequest"
              },
              "examples": {
                "textToVideo": {
                  "summary": "Text to video",
                  "value": {
                    "prompt": "Cinematic drone shot over Hanoi at dusk, neon reflections",
                    "model": "veo-3.1-lite",
                    "aspectRatio": "landscape",
                    "duration": 8
                  }
                },
                "imageToVideo": {
                  "summary": "Animate a still (first frame only)",
                  "value": {
                    "prompt": "The camera pushes slowly forward",
                    "model": "veo-3.1-lite",
                    "startImage": {
                      "url": "https://example.com/frame.jpg"
                    }
                  }
                },
                "interpolation": {
                  "summary": "Interpolate between two frames",
                  "value": {
                    "model": "veo-3.1-lite",
                    "startImage": {
                      "url": "https://example.com/first.jpg"
                    },
                    "endImage": {
                      "url": "https://example.com/last.jpg"
                    }
                  }
                },
                "ingredients": {
                  "summary": "Ingredients: images steer content and style, none becomes a frame",
                  "value": {
                    "prompt": "The character walks through the market",
                    "model": "omni-flash",
                    "resolution": "720p",
                    "referenceImage_1": {
                      "url": "https://example.com/character.png"
                    },
                    "referenceImage_2": {
                      "url": "https://example.com/style.png"
                    }
                  }
                },
                "variations": {
                  "summary": "Four variations in one task, returned in media[]",
                  "value": {
                    "prompt": "A weather vane turning slowly against a grey sky",
                    "model": "veo-3.1-lite",
                    "count": 4
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "flowVideoGenerated": {
            "{$request.body#/replyUrl}": {
              "post": {
                "summary": "Flow video generation finished",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/FlowCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback accepted."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flow/jobs/{jobId}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "getFlowJob",
        "summary": "Poll a Flow task",
        "description": "Fetch the state of a task created by `POST /api/v1/flow/images` or `POST /api/v1/flow/videos`. The task must belong to the calling service; a task of any other kind reports 404 rather than leaking that the id exists.",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "Task id returned by a Flow generate endpoint.",
            "schema": {
              "type": "string"
            },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          }
        ],
        "responses": {
          "200": {
            "description": "Flow task state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowRecordInfoEnvelope"
                },
                "examples": {
                  "single": {
                    "summary": "A task of count 1 — media[] holds one item and response is that item",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "kind": "flow_image",
                        "status": "SUCCESS",
                        "response": {
                          "media_id": "CAMaJDk0YmY5N2Q2LTQ5",
                          "project_id": "projects/1234567890",
                          "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999.jpg",
                          "url": "https://gateway.example.com/api/v1/flow/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                          "mime_type": "image/jpeg",
                          "bytes": 284119,
                          "width": 1376,
                          "height": 768,
                          "status": "succeeded"
                        },
                        "media": [
                          {
                            "media_id": "CAMaJDk0YmY5N2Q2LTQ5",
                            "project_id": "projects/1234567890",
                            "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999.jpg",
                            "url": "https://gateway.example.com/api/v1/flow/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                            "mime_type": "image/jpeg",
                            "bytes": 284119,
                            "width": 1376,
                            "height": 768,
                            "status": "succeeded"
                          }
                        ],
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  },
                  "multiple": {
                    "summary": "A task of count 2 — response stays media[0]",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "kind": "flow_image",
                        "status": "SUCCESS",
                        "response": {
                          "media_id": "CAMaJDk0YmY5N2Q2LTQ5",
                          "project_id": "projects/1234567890",
                          "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999.jpg",
                          "url": "https://gateway.example.com/api/v1/flow/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                          "mime_type": "image/jpeg",
                          "bytes": 284119,
                          "width": 1376,
                          "height": 768,
                          "status": "succeeded"
                        },
                        "media": [
                          {
                            "media_id": "CAMaJDk0YmY5N2Q2LTQ5",
                            "project_id": "projects/1234567890",
                            "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999.jpg",
                            "url": "https://gateway.example.com/api/v1/flow/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                            "mime_type": "image/jpeg",
                            "bytes": 284119,
                            "width": 1376,
                            "height": 768,
                            "status": "succeeded"
                          },
                          {
                            "media_id": "CAMaJGZlMzBiYTAxLTdi",
                            "project_id": "projects/1234567890",
                            "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999-1.jpg",
                            "url": "https://gateway.example.com/api/v1/flow/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999/1",
                            "mime_type": "image/jpeg",
                            "bytes": 291504,
                            "width": 1376,
                            "height": 768,
                            "status": "succeeded"
                          }
                        ],
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  },
                  "pending": {
                    "summary": "Still running — media[] is empty, never null",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "kind": "flow_video",
                        "status": "PENDING",
                        "response": null,
                        "media": [],
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flow/file/{taskId}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "downloadFlowFile",
        "summary": "Download a generated Flow image or video",
        "description": "Stream the generated bytes. This route is deliberately unauthenticated and addressed by the unguessable task UUID, because an `<img>` or `<video>` src cannot attach a bearer token. Range and conditional requests are supported.\n\nFiles are deleted after `ggflow.file_retention`; this route then answers 404 while the task record stays readable. Download promptly or copy the bytes to your own storage.",
        "security": [],
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "Task id of a succeeded Flow generation.",
            "schema": {
              "type": "string"
            },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes.",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flow/file/{taskId}/{index}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "downloadFlowFileItem",
        "summary": "Download one item of a multi-item Flow task",
        "description": "Item `index` of the task's `media[]`, counting from 0. Item 0 is also served by the path without an index, which is the url every single-item task returns and every result stored before `count` existed still carries.\n\nUnauthenticated, like the bare route: an `<img>` or `<video>` src cannot send a bearer token. The task id is the capability.",
        "security": [],
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "Task id of a succeeded Flow generation.",
            "schema": {
              "type": "string"
            },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          },
          {
            "name": "index",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Position in media[]. Out of range is a 404.",
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes.",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/grok/images": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "generateGrokImage",
        "summary": "Generate images",
        "description": "Submit an image generation to Grok Imagine.\n\nUpstream renders the whole batch inside one request, so `count` costs one generation rather than one per image.\n\nReturns 503 when the gateway is deployed with `grok.enabled: false`. Unlike the video route, this one does not need a browser-backed solver: the imagine socket is not behind Cloudflare.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrokImageRequest"
              },
              "examples": {
                "single": {
                  "summary": "One image from a prompt",
                  "value": {
                    "prompt": "A red bicycle leaning on a whitewashed wall, golden hour",
                    "aspectRatio": "2:3"
                  }
                },
                "batch": {
                  "summary": "Four images on the quality tier",
                  "value": {
                    "prompt": "Concept art of a floating market at dawn",
                    "model": "grok-imagine-pro",
                    "aspectRatio": "landscape",
                    "count": 4,
                    "replyUrl": "https://my-service.example.com/grok-callback"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "grokImageGenerated": {
            "{$request.body#/replyUrl}": {
              "post": {
                "summary": "Grok image generation finished",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/GrokCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback accepted."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/grok/videos": {
      "post": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "generateGrokVideo",
        "summary": "Generate a video",
        "description": "Submit a video generation to Grok Imagine. One request produces one clip.\n\nThis route additionally needs a Cloudflare clearance, which the gateway mints in a real browser through the solver service. A deployment without `captcha.url` and a browser endpoint serves images normally and fails every video with `UPSTREAM_AUTH`.\n\nReturns 503 when the gateway is deployed with `grok.enabled: false`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrokVideoRequest"
              },
              "examples": {
                "landscape": {
                  "summary": "A 6-second landscape clip",
                  "value": {
                    "prompt": "Cinematic drone shot over Hanoi at dusk",
                    "aspectRatio": "landscape",
                    "duration": 6,
                    "resolution": "480p"
                  }
                },
                "vertical": {
                  "summary": "Vertical, higher rendition",
                  "value": {
                    "prompt": "Neon rain on a Tokyo side street",
                    "aspectRatio": "portrait",
                    "resolution": "720p",
                    "replyUrl": "https://my-service.example.com/grok-callback"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generation accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/QueueUnavailable"
          }
        },
        "callbacks": {
          "grokVideoGenerated": {
            "{$request.body#/replyUrl}": {
              "post": {
                "summary": "Grok video generation finished",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/GrokCallbackEnvelope"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback accepted."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/grok/jobs/{jobId}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "getGrokJob",
        "summary": "Poll a Grok task",
        "description": "Fetch the state of a task created by `POST /api/v1/grok/images` or `POST /api/v1/grok/videos`. The task must belong to the calling service; a task of any other kind reports 404 rather than leaking that the id exists.",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "Task id returned by a Grok generate endpoint.",
            "schema": {
              "type": "string"
            },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          }
        ],
        "responses": {
          "200": {
            "description": "Grok task state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrokRecordInfoEnvelope"
                },
                "examples": {
                  "single": {
                    "summary": "A finished single-image task",
                    "value": {
                      "code": 200,
                      "msg": "success",
                      "data": {
                        "taskId": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                        "kind": "grok_image",
                        "status": "SUCCESS",
                        "response": {
                          "asset_id": "7aec87f6-e946-4b41-9073-123eab177286",
                          "conversation_id": "edb040b4-0d14-4e0d-9c38-534d835024e8",
                          "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999.jpg",
                          "url": "https://gateway.example.com/api/v1/grok/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                          "mime_type": "image/jpeg",
                          "bytes": 144424,
                          "width": 768,
                          "height": 1152,
                          "status": "succeeded"
                        },
                        "media": [
                          {
                            "asset_id": "7aec87f6-e946-4b41-9073-123eab177286",
                            "conversation_id": "edb040b4-0d14-4e0d-9c38-534d835024e8",
                            "file_name": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999.jpg",
                            "url": "https://gateway.example.com/api/v1/grok/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999",
                            "mime_type": "image/jpeg",
                            "bytes": 144424,
                            "width": 768,
                            "height": 1152,
                            "status": "succeeded"
                          }
                        ],
                        "errorCode": null,
                        "errorMessage": null
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/grok/file/{taskId}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "downloadGrokFile",
        "summary": "Download a generated Grok image or video",
        "description": "Stream the generated bytes. This route is deliberately unauthenticated and addressed by the unguessable task UUID, because an `<img>` or `<video>` src cannot attach a bearer token. Range and conditional requests are supported.\n\nFiles are deleted after `grok.file_retention`; this route then answers 404 while the task record stays readable. Download promptly or copy the bytes to your own storage.\n\nA task of `count` > 1 addresses its later items at `/api/v1/grok/file/{taskId}/{index}`; index 0 is this path.",
        "security": [],
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "Task id of a succeeded Grok generation.",
            "schema": {
              "type": "string"
            },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes.",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/grok/file/{taskId}/{index}": {
      "get": {
        "tags": [
          "Provider-native generation"
        ],
        "operationId": "getGrokFileItem",
        "summary": "Download one item of a multi-image Grok task",
        "description": "Item _n_ of a task that generated several images, zero-based, on the same unauthenticated route as the single-item form and for the same reason: an `<img>` src cannot carry a bearer token.\n\nGrok renders up to four images per submit, so the indexless route hands back the first and this one reaches the rest. It mirrors /api/v1/flow/file/{taskId}/{index} field for field.",
        "security": [],
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "Task id of a succeeded Flow generation.",
            "schema": {
              "type": "string"
            },
            "example": "0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          },
          {
            "name": "index",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Position in media[]. Out of range is a 404.",
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes.",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/generate/credit": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getRemainingCredits",
        "summary": "Suno credits left across the pool",
        "description": "sunoapi.org-compatible: `data` is one integer, the cached upstream credits across the gateway's enabled Suno accounts.\n\nThe total describes the whole pool, so only callers listed in the gateway's `auth.credit_readers` may read it; every other service token gets `403`. It is a cache refreshed from Suno periodically and debited as tasks spend.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditEnvelope"
                },
                "example": {
                  "code": 200,
                  "msg": "success",
                  "data": 4820
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "This service token is not listed in `auth.credit_readers`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "code": 403,
                  "msg": "this service token may not read pool credits"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/generate/credit/usage": {
      "get": {
        "tags": [
          "Suno operations"
        ],
        "operationId": "getCreditUsage",
        "summary": "Suno credits your tasks spent in a date range",
        "description": "Sums the credits recorded on each of the calling service's own tasks when it spent them, per job kind. Other services' tasks are never counted. Only tasks that ran after this endpoint was deployed are counted. Ranges are at most 366 days.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "RFC 3339 instant, or `YYYY-MM-DD` meaning the start of that day in Asia/Ho_Chi_Minh. Defaults to 30 days ago.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "RFC 3339 instant (exclusive), or `YYYY-MM-DD` meaning through the end of that day in Asia/Ho_Chi_Minh. Defaults to now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditUsageEnvelope"
                },
                "example": {
                  "code": 200,
                  "msg": "success",
                  "data": {
                    "from": "2026-09-30T17:00:00Z",
                    "to": "2026-10-22T17:00:00Z",
                    "credits": 1240,
                    "unconfirmedCredits": 10,
                    "jobs": 79,
                    "kinds": {
                      "generation": 1150,
                      "vocal_removal": 100
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "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"
                }
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "GenerateRequest": {
        "type": "object",
        "required": [
          "customMode",
          "instrumental",
          "model"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Free-form description of the song. Required except when customMode and instrumental are both true. 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`. Send `AUTO` unless you need a specific generation: it routes to any healthy account and submits that account's current upstream default, so it keeps working when Suno retires a generation. Pinned keys are currently `V6`, `V6_WILD`, or `V6_MINI`; names of retired generations (`V4`, `V4_5`, `V4_5PLUS`, `V4_5ALL`, `V5`, `V5_5`) are still accepted and served by `V6` (or `V6_MINI` when only free-tier accounts are available).",
            "example": "AUTO"
          },
          "customMode": {
            "type": "boolean",
            "description": "Required. When true, `style` and `title` become required, and `prompt` is treated as lyrics text when not instrumental."
          },
          "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",
            "description": "Required. Generate without vocals. In custom instrumental mode, `prompt` may be omitted."
          },
          "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"
          },
          "personaId": {
            "type": "string",
            "description": "Only available when custom parameters are enabled. Persona ID to apply to the generated music. Optional. Use a persona created via POST /api/v1/generate/generate-persona."
          },
          "personaModel": {
            "type": "string",
            "description": "Persona model type to apply when using personaId. `style_persona` (default) for personas from Generate Persona; `voice_persona` for voice personas."
          },
          "duration": {
            "type": "integer",
            "description": "Audio duration in seconds. Optional. Only supported when model is AUTO or V6 (or the retired V5_5) and customMode is true. Range: 10-360 seconds."
          },
          "vocalGender": {
            "type": "string",
            "description": "Preferred vocal gender. Allowed values: 'm' (male), 'f' (female)."
          },
          "styleWeight": {
            "type": "number",
            "format": "double",
            "description": "Weight of the provided style guidance. Range 0.00-1.00."
          },
          "weirdnessConstraint": {
            "type": "number",
            "format": "double",
            "description": "Constraint on creative deviation/novelty. Range 0.00-1.00."
          },
          "audioWeight": {
            "type": "number",
            "format": "double",
            "description": "Weight of the input audio influence (where applicable). Range 0.00-1.00."
          }
        }
      },
      "GenerateSoundsRequest": {
        "type": "object",
        "required": [
          "prompt",
          "model"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Description of the desired sound effect or loop."
          },
          "model": {
            "type": "string",
            "enum": [
              "AUTO",
              "V6",
              "V5"
            ],
            "description": "Sounds support AUTO and V6; the retired V5 name is still accepted and served by V6."
          },
          "soundLoop": {
            "type": "boolean",
            "default": false,
            "description": "Generate a seamlessly loopable sound."
          },
          "soundTempo": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 300,
            "description": "Optional BPM; omit or use null for Auto."
          },
          "soundKey": {
            "type": "string",
            "default": "Any",
            "enum": [
              "Any",
              "Cm",
              "C#m",
              "Dm",
              "D#m",
              "Em",
              "Fm",
              "F#m",
              "Gm",
              "G#m",
              "Am",
              "A#m",
              "Bm",
              "C",
              "C#",
              "D",
              "D#",
              "E",
              "F",
              "F#",
              "G",
              "G#",
              "A",
              "A#",
              "B"
            ]
          },
          "grabLyrics": {
            "type": "boolean",
            "default": false,
            "description": "Best-effort v3 alignment when a completed sound clip exposes lyrics. Accepted but has no effect for clips with no lyric text."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Optional callback URL receiving staged sound callbacks."
          }
        }
      },
      "AddInstrumentalRequest": {
        "type": "object",
        "required": [
          "uploadUrl",
          "title",
          "tags",
          "negativeTags"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "Publicly reachable HTTP(S) audio URL. Private and loopback destinations are rejected."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Title for the generated track."
          },
          "tags": {
            "type": "string",
            "minLength": 1,
            "description": "Desired style, mood, and instrumental characteristics."
          },
          "negativeTags": {
            "type": "string",
            "description": "Styles or traits to exclude. The key is required; an empty value is allowed."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "URL receiving staged music callbacks."
          },
          "model": {
            "type": "string",
            "default": "V4_5PLUS",
            "description": "Generation model. Defaults to V4_5PLUS."
          },
          "vocalGender": {
            "type": "string",
            "description": "Preferred vocal gender. Allowed values: 'm' (male), 'f' (female)."
          },
          "styleWeight": {
            "type": "number",
            "format": "double",
            "description": "Weight of the provided style guidance. Range 0.00-1.00."
          },
          "weirdnessConstraint": {
            "type": "number",
            "format": "double",
            "description": "Constraint on creative deviation/novelty. Range 0.00-1.00."
          },
          "audioWeight": {
            "type": "number",
            "format": "double",
            "description": "Weight of the input audio influence (where applicable). Range 0.00-1.00."
          }
        }
      },
      "AddVocalsRequest": {
        "type": "object",
        "required": [
          "uploadUrl",
          "prompt",
          "title",
          "style",
          "negativeTags"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "Publicly reachable HTTP(S) audio URL."
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "description": "Vocal lyrics or instructions sent as the custom-mode prompt."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Title for the generated track."
          },
          "style": {
            "type": "string",
            "minLength": 1,
            "description": "Music and vocal style sent as generation tags."
          },
          "negativeTags": {
            "type": "string",
            "description": "Styles or vocal traits to exclude. The key is required; an empty value is allowed."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "URL receiving staged music callbacks."
          },
          "model": {
            "type": "string",
            "default": "V4_5PLUS",
            "description": "Generation model. Defaults to V4_5PLUS."
          },
          "vocalGender": {
            "type": "string",
            "enum": [
              "m",
              "f"
            ],
            "description": "Compatibility field; currently accepted but ignored."
          },
          "styleWeight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "weirdnessConstraint": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "audioWeight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          }
        }
      },
      "UploadCoverRequest": {
        "type": "object",
        "required": [
          "uploadUrl",
          "customMode",
          "instrumental",
          "model"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "Publicly reachable HTTP(S) audio URL."
          },
          "prompt": {
            "type": "string",
            "description": "Required in non-custom mode and in custom vocal mode. Exact lyrics in custom mode; description in non-custom mode."
          },
          "style": {
            "type": "string",
            "description": "Required in custom mode; omit in non-custom mode."
          },
          "title": {
            "type": "string",
            "description": "Required in custom mode; omit in non-custom mode."
          },
          "customMode": {
            "type": "boolean",
            "description": "Select exact custom fields or a non-custom description prompt."
          },
          "instrumental": {
            "type": "boolean",
            "description": "Custom instrumental mode permits an empty prompt."
          },
          "model": {
            "type": "string",
            "minLength": 1,
            "description": "Generation model key."
          },
          "negativeTags": {
            "type": "string",
            "description": "Styles or traits to exclude."
          },
          "personaId": {
            "type": "string",
            "description": "Compatibility field; currently accepted but ignored."
          },
          "personaModel": {
            "type": "string",
            "enum": [
              "style_persona",
              "voice_persona"
            ],
            "description": "Compatibility field; currently accepted but ignored."
          },
          "vocalGender": {
            "type": "string",
            "enum": [
              "m",
              "f"
            ],
            "description": "Compatibility field; currently accepted but ignored."
          },
          "styleWeight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "weirdnessConstraint": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "audioWeight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "duration": {
            "type": "integer",
            "minimum": 10,
            "maximum": 360,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "URL receiving staged music callbacks."
          }
        }
      },
      "UploadExtendRequest": {
        "type": "object",
        "required": [
          "uploadUrl",
          "defaultParamFlag",
          "model"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "Publicly reachable HTTP(S) audio URL."
          },
          "defaultParamFlag": {
            "type": "boolean",
            "description": "True selects the captured custom flow. False means inherit source parameters and is temporarily rejected until its exact continuation policy is verified."
          },
          "instrumental": {
            "type": "boolean",
            "default": false
          },
          "prompt": {
            "type": "string",
            "description": "Optional extension prompt. Character limit depends on model."
          },
          "style": {
            "type": "string",
            "description": "Required in custom mode; omit in non-custom mode."
          },
          "title": {
            "type": "string",
            "description": "Required in custom mode; omit in non-custom mode."
          },
          "continueAt": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Required in custom mode; omit in non-custom mode."
          },
          "model": {
            "type": "string",
            "minLength": 1,
            "description": "Generation model key."
          },
          "negativeTags": {
            "type": "string",
            "description": "Styles or traits to exclude."
          },
          "personaId": {
            "type": "string",
            "description": "Compatibility field; currently accepted but ignored."
          },
          "personaModel": {
            "type": "string",
            "enum": [
              "style_persona",
              "voice_persona"
            ],
            "description": "Compatibility field; currently accepted but ignored."
          },
          "vocalGender": {
            "type": "string",
            "enum": [
              "m",
              "f"
            ],
            "description": "Compatibility field; currently accepted but ignored."
          },
          "styleWeight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "weirdnessConstraint": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "audioWeight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Compatibility field; currently accepted but ignored."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "description": "URL receiving staged music callbacks."
          }
        }
      },
      "ExtendRequest": {
        "type": "object",
        "required": [
          "defaultParamFlag",
          "audioId",
          "model"
        ],
        "properties": {
          "defaultParamFlag": {
            "type": "boolean",
            "description": "False inherits the source parameters and duration. True uses the custom parameters in this request."
          },
          "instrumental": {
            "type": "boolean",
            "default": false,
            "description": "Custom mode only. When true, prompt must be omitted."
          },
          "audioId": {
            "type": "string",
            "description": "Source clip `id` from `response.sunoData[]`. It must belong to the calling service.",
            "example": "95363029-a760-4a9a-98e7-33dbadc07be0"
          },
          "prompt": {
            "type": "string",
            "description": "Custom lyrics. Required when defaultParamFlag is true and instrumental is false."
          },
          "style": {
            "type": "string",
            "description": "Custom style tags. Required when defaultParamFlag is true."
          },
          "title": {
            "type": "string",
            "description": "Custom title. Required when defaultParamFlag is true."
          },
          "continueAt": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Custom continuation position in seconds. Required when defaultParamFlag is true.",
            "example": 30.5
          },
          "model": {
            "type": "string",
            "description": "Model key or public alias; it must resolve to the source clip's model, or be `AUTO` to continue on the admitted account's default.",
            "example": "AUTO"
          },
          "negativeTags": {
            "type": "string",
            "description": "Custom styles or traits to exclude."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Optional callback URL receiving staged music callbacks. Omit to use polling only."
          },
          "personaId": {
            "type": "string",
            "description": "Only available when custom parameters are enabled. Persona ID to apply to the generated music. Optional. Use a persona created via POST /api/v1/generate/generate-persona."
          },
          "personaModel": {
            "type": "string",
            "description": "Persona model type to apply when using personaId. `style_persona` (default) for personas from Generate Persona; `voice_persona` for voice personas."
          },
          "vocalGender": {
            "type": "string",
            "description": "Preferred vocal gender. Allowed values: 'm' (male), 'f' (female)."
          },
          "styleWeight": {
            "type": "number",
            "format": "double",
            "description": "Weight of the provided style guidance. Range 0.00-1.00."
          },
          "weirdnessConstraint": {
            "type": "number",
            "format": "double",
            "description": "Constraint on creative deviation/novelty. Range 0.00-1.00."
          },
          "audioWeight": {
            "type": "number",
            "format": "double",
            "description": "Weight of the input audio influence (where applicable). Range 0.00-1.00."
          }
        }
      },
      "LyricsRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 200,
            "description": "Description of the desired theme, mood, style, and song structure.",
            "example": "bittersweet summer song about a coastal town"
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "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"
              }
            }
          }
        }
      },
      "VocalRemovalGenerateRequest": {
        "type": "object",
        "required": [],
        "oneOf": [
          {
            "title": "Existing gateway audio",
            "required": [
              "taskId",
              "audioId"
            ]
          },
          {
            "title": "Public audio URL",
            "required": [
              "audioUrl"
            ]
          }
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Gateway music task ID. Required with audioId."
          },
          "audioId": {
            "type": "string",
            "description": "Generated clip ID. Required with taskId and mutually exclusive with audioUrl."
          },
          "audioUrl": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTP(S) audio URL, at most 20 MB. Mutually exclusive with taskId/audioId."
          },
          "type": {
            "type": "string",
            "enum": [
              "separate_vocal",
              "split_stem",
              "split_stem_advanced"
            ],
            "default": "separate_vocal"
          },
          "stemName": {
            "type": "string",
            "enum": [
              "Lead Vocal",
              "Drum Kit",
              "Kick",
              "Snare",
              "Risers",
              "Bass",
              "Backing Vocals",
              "Piano",
              "Electric Guitar",
              "Percussion",
              "String Section",
              "Synth",
              "Acoustic Guitar",
              "Sound Effects",
              "Synth Pad",
              "Synth Bass",
              "Guitar",
              "Brass Section",
              "Organ",
              "Electronic Drum Kit",
              "Lead Electric Guitar",
              "Synth Keys",
              "Rhythm Electric Guitar",
              "Electric Piano",
              "Upright Bass",
              "Keyboards",
              "Distorted Electric Guitar",
              "Synth Strings",
              "Synth Lead",
              "Woodwinds",
              "Rhythm Acoustic Guitar",
              "Flute",
              "Harp",
              "Tambourine",
              "Trumpet",
              "Arpeggiator",
              "Accordion",
              "Fiddle",
              "Pedal Steel Guitar",
              "Synth Voice",
              "Violin",
              "Digital Piano",
              "Synth Brass",
              "Mandolin",
              "Choir",
              "Banjo",
              "Bells",
              "Clarinet",
              "Tenor Saxophone",
              "Trombone",
              "Shaker",
              "French Horn",
              "Glockenspiel",
              "Electric Bass",
              "Cello",
              "Timpani",
              "Harmonica",
              "Marimba",
              "Vibraphone",
              "Lap Steel Guitar",
              "Saxophone",
              "Orchestra",
              "Horns",
              "Cymbals",
              "Hand Clap",
              "Oboe",
              "Celesta",
              "Congas",
              "Drone",
              "Alto Saxophone",
              "Double Bass",
              "Ukulele",
              "Harpsichord",
              "Baritone Saxophone",
              "Xylophone",
              "Tuba",
              "Bass Guitar",
              "Whistle",
              "Lead Guitar",
              "Rhodes",
              "808",
              "Bongos",
              "Bassoon",
              "Cowbell",
              "Viola",
              "Sitar",
              "Steel Drums",
              "Piccolo",
              "Theremin",
              "Bagpipes",
              "Hi-Hat",
              "Music Box",
              "Melodica",
              "Tabla",
              "Koto",
              "Djembe",
              "Taiko",
              "Didgeridoo"
            ],
            "description": "Required only for split_stem_advanced and forwarded verbatim to the captured stem fields."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1
          }
        }
      },
      "VocalRemovalCallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "task_id": {
                "type": "string"
              },
              "vocal_removal_info": {
                "type": "object",
                "properties": {
                  "origin_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "instrumental_url": {
                    "type": "string"
                  },
                  "vocal_url": {
                    "type": "string"
                  },
                  "backing_vocals_url": {
                    "type": "string"
                  },
                  "drums_url": {
                    "type": "string"
                  },
                  "bass_url": {
                    "type": "string"
                  },
                  "guitar_url": {
                    "type": "string"
                  },
                  "keyboard_url": {
                    "type": "string"
                  },
                  "percussion_url": {
                    "type": "string"
                  },
                  "strings_url": {
                    "type": "string"
                  },
                  "synth_url": {
                    "type": "string"
                  },
                  "fx_url": {
                    "type": "string"
                  },
                  "brass_url": {
                    "type": "string"
                  },
                  "woodwinds_url": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "VocalRemovalRecordInfoEnvelope": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SubmitEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "taskId": {
                    "type": "string"
                  },
                  "musicId": {
                    "type": "string"
                  },
                  "musicIndex": {
                    "type": "integer"
                  },
                  "callbackUrl": {
                    "type": "string"
                  },
                  "completeTime": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "response": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "originUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "originData": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "duration": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "audio_url": {
                              "type": "string"
                            },
                            "stem_type_group_name": {
                              "type": "string"
                            },
                            "id": {
                              "type": "string"
                            }
                          }
                        }
                      },
                      "instrumentalUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "vocalUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "backingVocalsUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "drumsUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "bassUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "guitarUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "keyboardUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "percussionUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "stringsUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "synthUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "fxUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "brassUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "woodwindsUrl": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  },
                  "successFlag": {
                    "type": "string",
                    "enum": [
                      "PENDING",
                      "SUCCESS",
                      "CREATE_TASK_FAILED",
                      "GENERATE_AUDIO_FAILED",
                      "CALLBACK_EXCEPTION"
                    ]
                  },
                  "createTime": {
                    "type": "string"
                  },
                  "errorCode": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "errorMessage": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "MidiGenerateRequest": {
        "type": "object",
        "required": [
          "taskId"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Completed vocal-removal task ID."
          },
          "audioId": {
            "type": "string",
            "description": "Optional separated track ID from originData. Omit to process every separated track."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1
          }
        }
      },
      "MidiCallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "taskId": {
                "type": "string"
              },
              "midiData": {
                "$ref": "#/components/schemas/MidiData"
              }
            }
          }
        }
      },
      "MidiNote": {
        "type": "object",
        "required": [
          "pitch",
          "start",
          "end",
          "velocity"
        ],
        "properties": {
          "pitch": {
            "type": "integer",
            "minimum": 0,
            "maximum": 127
          },
          "start": {
            "type": "number"
          },
          "end": {
            "type": "number"
          },
          "velocity": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        }
      },
      "MidiInstrument": {
        "type": "object",
        "required": [
          "name",
          "notes"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "notes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MidiNote"
            }
          }
        }
      },
      "MidiData": {
        "type": "object",
        "required": [
          "state",
          "instruments"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "complete"
            ]
          },
          "instruments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MidiInstrument"
            }
          }
        }
      },
      "MidiRecordInfoEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "taskId": {
                "type": "string"
              },
              "recordTaskId": {
                "type": "integer"
              },
              "audioId": {
                "type": "string"
              },
              "callbackUrl": {
                "type": "string"
              },
              "completeTime": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "midiData": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/MidiData"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "successFlag": {
                "type": "integer",
                "enum": [
                  0,
                  1,
                  2,
                  3
                ]
              },
              "createTime": {
                "type": "integer"
              },
              "errorCode": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "errorMessage": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "CoverArtGenerateRequest": {
        "type": "object",
        "required": [
          "taskId"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Completed owned music-generation task ID."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "minLength": 1
          }
        }
      },
      "CoverArtCallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "enum": [
              200,
              501
            ]
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": [
              "taskId",
              "images"
            ],
            "properties": {
              "taskId": {
                "type": "string"
              },
              "images": {
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        }
      },
      "CoverArtRecordInfoEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": [
              "taskId",
              "parentTaskId",
              "callbackUrl",
              "completeTime",
              "response",
              "successFlag",
              "createTime",
              "errorCode",
              "errorMessage"
            ],
            "properties": {
              "taskId": {
                "type": "string"
              },
              "parentTaskId": {
                "type": "string"
              },
              "callbackUrl": {
                "type": "string"
              },
              "completeTime": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "response": {
                "type": "object",
                "properties": {
                  "images": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uri"
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              "successFlag": {
                "type": "integer",
                "enum": [
                  0,
                  1,
                  2,
                  3
                ]
              },
              "createTime": {
                "type": "string",
                "format": "date-time"
              },
              "errorCode": {
                "type": [
                  "integer",
                  "null"
                ],
                "enum": [
                  200,
                  500,
                  null
                ]
              },
              "errorMessage": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "MashupGenerateRequest": {
        "type": "object",
        "required": [
          "uploadUrlList",
          "customMode",
          "model"
        ],
        "properties": {
          "uploadUrlList": {
            "type": "array",
            "description": "Array containing exactly 2 audio file URLs to be mashed up together. Both URLs must be valid and accessible.",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "minItems": 2,
            "maxItems": 2,
            "example": [
              "https://storage.example.com/audio1.mp3",
              "https://storage.example.com/audio2.mp3"
            ]
          },
          "customMode": {
            "type": "boolean",
            "description": "Custom mode is required in this version. `true` requires style and title, plus prompt for vocal Mashup. `false` is part of the public compatibility surface but is temporarily rejected until captured.",
            "example": true
          },
          "prompt": {
            "type": "string",
            "description": "A description of the desired audio content.  \n- In Custom Mode (`customMode: true`): Required if `instrumental` is `false`. The prompt will be strictly used as the lyrics and sung in the generated track. Character limits by model:  \n  - **V4**: Maximum 3000 characters  \n  - **V4_5, V4_5PLUS, V4_5ALL, V5, V5_5 & V6**: Maximum 5000 characters  \n  Example: \"A calm and relaxing piano track with soft melodies\"  \n- In Non-custom Mode (`customMode: false`): Always required. The prompt serves as the core idea, and lyrics will be automatically generated based on it (not strictly matching the input). Maximum 500 characters.  \n  Example: \"A short relaxing piano tune\"",
            "example": "A calm and relaxing piano track with soft melodies"
          },
          "style": {
            "type": "string",
            "description": "The music style or genre for the audio.  \n- Required in Custom Mode (`customMode: true`). Examples: \"Jazz\", \"Classical\", \"Electronic\". Character limits by model:  \n  - **V4**: Maximum 200 characters  \n  - **V4_5, V4_5PLUS, V4_5ALL, V5, V5_5 & V6**: Maximum 1000 characters  \n  Example: \"Classical\"  \n- In Non-custom Mode (`customMode: false`): Leave empty.",
            "example": "Classical"
          },
          "title": {
            "type": "string",
            "description": "The title of the generated music track.  \n- Required in Custom Mode (`customMode: true`). Character limits by model:  \n  - **V4 & V4_5ALL**: Maximum 80 characters  \n  - **V4_5, V4_5PLUS, V5, V5_5 & V6**: Maximum 100 characters  \n  Example: \"Peaceful Piano Meditation\"  \n- In Non-custom Mode (`customMode: false`): Leave empty.",
            "maxLength": 100,
            "example": "Peaceful Piano Meditation"
          },
          "instrumental": {
            "type": "boolean",
            "description": "Determines if the audio should be instrumental (no lyrics).  \n- In Custom Mode (`customMode: true`):  \n  - If `true`: Only `style` and `title` are required.  \n  - If `false`: `style`, `title`, and `prompt` are required (with `prompt` used as the exact lyrics).  \n- In Non-custom Mode (`customMode: false`): No impact on required fields (`prompt` only). Lyrics are auto-generated if `instrumental` is `false`.",
            "example": true
          },
          "model": {
            "type": "string",
            "description": "Only V5 is currently accepted for Mashup because it is the captured model variant.",
            "enum": [
              "V5"
            ],
            "example": "V5"
          },
          "vocalGender": {
            "type": "string",
            "description": "Preferred vocal gender. Accepted but currently ignored until an upstream mapping is captured.",
            "enum": [
              "m",
              "f"
            ],
            "example": "m"
          },
          "styleWeight": {
            "type": "number",
            "description": "Weight of the provided style guidance. Accepted but currently ignored until an upstream mapping is captured.",
            "minimum": 0,
            "maximum": 1,
            "multipleOf": 0.01,
            "example": 0.65
          },
          "weirdnessConstraint": {
            "type": "number",
            "description": "Creativity constraint. Accepted but currently ignored until an upstream mapping is captured.",
            "minimum": 0,
            "maximum": 1,
            "multipleOf": 0.01,
            "example": 0.65
          },
          "audioWeight": {
            "type": "number",
            "description": "Input-audio weight. Accepted but currently ignored until an upstream mapping is captured.",
            "minimum": 0,
            "maximum": 1,
            "multipleOf": 0.01,
            "example": 0.65
          },
          "duration": {
            "type": "integer",
            "description": "Optional duration in seconds. Accepted but currently ignored until an upstream mapping is captured.",
            "minimum": 10,
            "maximum": 360,
            "example": 20
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "description": "The URL to receive task completion notifications when mashup generation is complete.\n- For detailed callback format and implementation guide, see [Music Generation Callbacks](https://docs.sunoapi.org/suno-api/generate-music-callbacks)\n- Alternatively, you can use the get music generation details endpoint to poll task status",
            "example": "https://api.example.com/callback"
          }
        }
      },
      "WavGenerateRequest": {
        "type": "object",
        "required": [
          "taskId",
          "audioId"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "The music task that created the source track."
          },
          "audioId": {
            "type": "string",
            "description": "The specific track within the task to convert to WAV."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL receiving the single WAV completion notification."
          }
        }
      },
      "WavCallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "enum": [
              200,
              500
            ]
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "audioWavUrl",
                  "task_id"
                ],
                "properties": {
                  "audioWavUrl": {
                    "type": "string",
                    "description": "Signed WAV download URL."
                  },
                  "task_id": {
                    "type": "string",
                    "description": "The WAV conversion task id."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "WavRecordInfoEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "const": 200
          },
          "msg": {
            "type": "string",
            "const": "success"
          },
          "data": {
            "$ref": "#/components/schemas/WavRecordInfo"
          }
        }
      },
      "WavRecordInfo": {
        "type": "object",
        "properties": {
          "taskId": {
            "type": "string"
          },
          "musicId": {
            "type": "string",
            "description": "Source music track id that was converted."
          },
          "musicIndex": {
            "type": "integer",
            "description": "Position of the track within its task, 0 or 1."
          },
          "callbackUrl": {
            "type": "string"
          },
          "completeTime": {
            "type": [
              "string",
              "null"
            ],
            "description": "Completion timestamp; null while pending."
          },
          "response": {
            "type": "object",
            "properties": {
              "audioWavUrl": {
                "type": "string",
                "description": "Signed WAV download URL; empty until complete."
              }
            }
          },
          "successFlag": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "CREATE_TASK_FAILED",
              "GENERATE_WAV_FAILED"
            ]
          },
          "createTime": {
            "type": "string"
          },
          "errorCode": {
            "type": [
              "number",
              "null"
            ]
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Mp4GenerateRequest": {
        "type": "object",
        "required": [
          "taskId",
          "audioId"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "The music task that created the source track."
          },
          "audioId": {
            "type": "string",
            "description": "The specific track within the task to convert to video."
          },
          "author": {
            "type": "string",
            "maxLength": 50,
            "description": "Accepted for reference compatibility; not forwarded upstream."
          },
          "domainName": {
            "type": "string",
            "maxLength": 50,
            "description": "Accepted for reference compatibility; not forwarded upstream."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL receiving the single MP4 completion notification."
          }
        }
      },
      "Mp4CallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "enum": [
              200,
              500
            ]
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "task_id",
                  "video_url"
                ],
                "properties": {
                  "task_id": {
                    "type": "string",
                    "description": "The MP4 render task id."
                  },
                  "video_url": {
                    "type": "string",
                    "description": "Signed MP4 download URL."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Mp4RecordInfoEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "const": 200
          },
          "msg": {
            "type": "string",
            "const": "success"
          },
          "data": {
            "$ref": "#/components/schemas/Mp4RecordInfo"
          }
        }
      },
      "Mp4RecordInfo": {
        "type": "object",
        "properties": {
          "taskId": {
            "type": "string"
          },
          "musicId": {
            "type": "string",
            "description": "Source music track id that was converted."
          },
          "musicIndex": {
            "type": "integer",
            "description": "Position of the track within its task, 0 or 1."
          },
          "callbackUrl": {
            "type": "string"
          },
          "completeTime": {
            "type": [
              "string",
              "null"
            ],
            "description": "Completion timestamp; null while pending."
          },
          "response": {
            "type": "object",
            "properties": {
              "videoUrl": {
                "type": "string",
                "description": "Signed MP4 download URL; empty until complete."
              }
            }
          },
          "successFlag": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "CREATE_TASK_FAILED",
              "GENERATE_MP4_FAILED"
            ]
          },
          "createTime": {
            "type": "string"
          },
          "errorCode": {
            "type": [
              "number",
              "null"
            ]
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "LyricsCallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "enum": [
              200,
              400,
              451,
              500
            ]
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": [
              "callbackType",
              "taskId",
              "data"
            ],
            "properties": {
              "callbackType": {
                "type": "string",
                "enum": [
                  "complete",
                  "error"
                ]
              },
              "taskId": {
                "type": "string"
              },
              "data": {
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/LyricsVariant"
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        }
      },
      "MusicCallbackEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "enum": [
              200,
              400,
              451,
              500
            ]
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": [
              "callbackType",
              "task_id",
              "data"
            ],
            "properties": {
              "callbackType": {
                "type": "string",
                "enum": [
                  "text",
                  "first",
                  "complete",
                  "error"
                ]
              },
              "task_id": {
                "type": "string"
              },
              "data": {
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/MusicCallbackTrack"
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        }
      },
      "MusicCallbackTrack": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "audio_url": {
            "type": "string"
          },
          "source_audio_url": {
            "type": "string"
          },
          "stream_audio_url": {
            "type": "string"
          },
          "source_stream_audio_url": {
            "type": "string"
          },
          "image_url": {
            "type": "string"
          },
          "source_image_url": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "model_name": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "tags": {
            "type": "string"
          },
          "createTime": {
            "type": "string"
          },
          "duration": {
            "type": "number"
          }
        }
      },
      "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 returned by record-info polling and the current lyrics terminal callback.",
        "properties": {
          "taskId": {
            "type": "string",
            "description": "The task id."
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "TEXT_SUCCESS",
              "FIRST_SUCCESS",
              "SUCCESS",
              "GENERATE_AUDIO_FAILED",
              "GENERATE_LYRICS_FAILED",
              "SENSITIVE_WORD_ERROR",
              "CREATE_TASK_FAILED"
            ],
            "description": "Music: PENDING → TEXT_SUCCESS → FIRST_SUCCESS → SUCCESS | GENERATE_AUDIO_FAILED | CREATE_TASK_FAILED | SENSITIVE_WORD_ERROR. Lyrics: PENDING → SUCCESS | GENERATE_LYRICS_FAILED | SENSITIVE_WORD_ERROR | CREATE_TASK_FAILED."
          },
          "type": {
            "type": "string",
            "enum": [
              "GENERATE",
              "EXTEND",
              "COVER",
              "LYRICS"
            ],
            "description": "The kind of work this task performs."
          },
          "operationType": {
            "type": "string",
            "enum": [
              "generate",
              "extend",
              "cover",
              "lyrics"
            ],
            "description": "Lowercase operation name matching the task type."
          },
          "parentMusicId": {
            "type": "string",
            "description": "Source clip id for extension tasks; an empty string for normal generation."
          },
          "param": {
            "type": "string",
            "description": "The submitted input, rendered as a camelCase JSON string."
          },
          "response": {
            "type": "object",
            "description": "The result. Music tasks carry `sunoData[]`; lyrics tasks carry `data[]`. Arrays are empty until a result exists.",
            "properties": {
              "taskId": {
                "type": "string"
              },
              "sunoData": {
                "type": "array",
                "description": "Tracks. Empty until the status is SUCCESS.",
                "items": {
                  "$ref": "#/components/schemas/SunoTrack"
                }
              },
              "data": {
                "type": "array",
                "description": "Generated lyrics variants. This integration currently returns one variant.",
                "items": {
                  "$ref": "#/components/schemas/LyricsVariant"
                }
              }
            }
          },
          "errorCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set only on failed tasks; otherwise null."
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set only on failed tasks; otherwise null."
          },
          "creditsUsed": {
            "type": "integer",
            "minimum": 0,
            "description": "Suno upstream credits this task spent: the configured per-kind cost, applied when the paid request is sent (Suno reports no per-task cost). Present once known; `0` for a task that ended before the paid boundary; absent while a task has not spent yet. A recovered resubmission adds its own cost."
          }
        }
      },
      "SunoTrack": {
        "type": "object",
        "description": "One generated track.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Clip id—use as `audioId` for extension."
          },
          "title": {
            "type": "string",
            "description": "Track title; may be empty for simple generations."
          },
          "audioUrl": {
            "type": "string",
            "description": "Audio URL served by this gateway: `GET /api/v1/media/{clipId}` on `server.public_base_url` (a site-relative path when that is unset). Suno no longer publishes clip audio directly, so the gateway fetches and decrypts it on request, then transcodes to MP3 (`audio/mpeg`, 192 kbps) without storing anything. Append `?format=m4a` for Suno's own Opus-in-MP4 rendition, which is one lossy generation better. Empty until the clip is playable."
          },
          "streamAudioUrl": {
            "type": "string",
            "description": "Same gateway media URL as `audioUrl`; kept for compatibility with callers that use a separate streaming field."
          },
          "imageUrl": {
            "type": "string",
            "description": "Cover image URL."
          },
          "prompt": {
            "type": "string",
            "description": "Lyrics or prompt associated with the rendered track."
          },
          "modelName": {
            "type": "string",
            "description": "Upstream model that rendered the track, e.g. `V5`."
          },
          "tags": {
            "type": "string",
            "description": "Style tags associated with the rendered track."
          },
          "createTime": {
            "type": "string",
            "description": "Upstream clip creation time."
          },
          "duration": {
            "type": "number",
            "description": "Audio duration in seconds when upstream reports it."
          }
        }
      },
      "LyricsVariant": {
        "type": "object",
        "required": [
          "text",
          "title",
          "status",
          "errorMessage"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "Generated lyrics content."
          },
          "title": {
            "type": "string",
            "description": "Generated title; empty when upstream does not provide one."
          },
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "failed"
            ]
          },
          "errorMessage": {
            "type": "string",
            "description": "Failure detail; empty for a complete variant."
          }
        }
      },
      "TimestampedLyricsRequest": {
        "type": "object",
        "required": [
          "taskId",
          "audioId"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Gateway task that created the requested clip."
          },
          "audioId": {
            "type": "string",
            "description": "Generated clip whose word timings should be returned."
          }
        }
      },
      "TimestampedWord": {
        "type": "object",
        "required": [
          "word",
          "success",
          "startS",
          "endS",
          "palign"
        ],
        "properties": {
          "word": {
            "type": "string"
          },
          "success": {
            "type": "boolean"
          },
          "startS": {
            "type": "number",
            "description": "Word start time in seconds."
          },
          "endS": {
            "type": "number",
            "description": "Word end time in seconds."
          },
          "palign": {
            "type": "number",
            "description": "Upstream alignment confidence parameter."
          }
        }
      },
      "TimestampedLyricsEnvelope": {
        "type": "object",
        "required": [
          "code",
          "msg",
          "data"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "const": 200
          },
          "msg": {
            "type": "string",
            "const": "success"
          },
          "data": {
            "type": "object",
            "required": [
              "alignedWords",
              "waveformData",
              "hootCer",
              "isStreamed"
            ],
            "properties": {
              "alignedWords": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TimestampedWord"
                }
              },
              "waveformData": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "description": "Currently an empty array: captured v3 returns multi-resolution waveform aggregates, not this public flat representation."
              },
              "hootCer": {
                "type": "number",
                "description": "Currently 0 because captured v3 does not return a public-compatible alignment error score."
              },
              "isStreamed": {
                "type": "boolean",
                "description": "Currently false because captured v3 does not return this field."
              }
            }
          }
        }
      },
      "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`. Standard models use public aliases; custom models retain their raw key.",
            "example": "V5"
          },
          "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"
          }
        }
      },
      "GeneratePersonaRequest": {
        "type": "object",
        "required": [
          "taskId",
          "audioId",
          "name",
          "description"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Original music task that owns the source clip."
          },
          "audioId": {
            "type": "string",
            "description": "Source clip the persona is derived from."
          },
          "name": {
            "type": "string",
            "description": "Persona name."
          },
          "description": {
            "type": "string",
            "description": "Persona description."
          },
          "vocalStart": {
            "type": "number",
            "format": "double",
            "description": "Vocal sample start in seconds. Defaults to 0.0."
          },
          "vocalEnd": {
            "type": "number",
            "format": "double",
            "description": "Vocal sample end in seconds. Defaults to 30.0."
          },
          "style": {
            "type": "string",
            "description": "Style text forwarded as user_input_styles."
          }
        }
      },
      "PersonaEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "msg": {
            "type": "string",
            "example": "success"
          },
          "data": {
            "type": "object",
            "properties": {
              "personaId": {
                "type": "string",
                "description": "Created persona ID."
              },
              "name": {
                "type": "string",
                "description": "Persona name."
              }
            }
          }
        }
      },
      "ReplaceSectionRequest": {
        "type": "object",
        "required": [
          "prompt",
          "tags",
          "title",
          "infillStartS",
          "infillEndS",
          "fullLyrics"
        ],
        "properties": {
          "taskId": {
            "type": "string",
            "description": "Mode 1: original task that owns the source clip."
          },
          "audioId": {
            "type": "string",
            "description": "Mode 1: source clip to infill."
          },
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "description": "Mode 2: publicly reachable HTTP(S) audio URL."
          },
          "model": {
            "type": "string",
            "minLength": 1,
            "description": "Mode 2: generation model key."
          },
          "prompt": {
            "type": "string",
            "description": "New lyrics for the replaced section (exact custom lyrics)."
          },
          "tags": {
            "type": "string",
            "description": "Comma-separated style tags."
          },
          "title": {
            "type": "string",
            "description": "Track title."
          },
          "negativeTags": {
            "type": "string",
            "description": "Styles or traits to exclude."
          },
          "infillStartS": {
            "type": "number",
            "format": "double",
            "description": "Replace-from timestamp in seconds. Window must be at least 10 seconds."
          },
          "infillEndS": {
            "type": "number",
            "format": "double",
            "description": "Replace-to timestamp in seconds."
          },
          "fullLyrics": {
            "type": "string",
            "description": "Current full lyrics used to derive the out-of-window context."
          },
          "callBackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Optional completion callback; poll via GET /api/v1/generate/record-info instead."
          }
        }
      },
      "ImageGenerateRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 32000,
            "description": "What to draw. The whole request."
          },
          "model": {
            "type": "string",
            "description": "Accepted for client compatibility; ignored."
          },
          "callBackUrl": {
            "type": "string",
            "description": "Receives one completion notification."
          },
          "ratio": {
            "type": "string",
            "enum": [
              "1:1",
              "16:9",
              "9:16"
            ],
            "description": "Output shape. Upstream renders a fixed pixel budget, so only the ratio is honored; it is applied as verified prompt wording. Empty uses the upstream default (16:9-shaped landscape)."
          }
        }
      },
      "ImageResponse": {
        "type": "object",
        "properties": {
          "taskId": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string",
            "description": "Absolute URL of the generated image when server.public_base_url is set, otherwise a path. Valid for `chatgpt.image_retention` (default 7 days); 404 afterwards."
          },
          "mimeType": {
            "type": "string",
            "example": "image/png"
          },
          "sizeBytes": {
            "type": "integer"
          }
        }
      },
      "ImageRecordInfo": {
        "type": "object",
        "properties": {
          "taskId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "CREATE_TASK_FAILED",
              "GENERATE_IMAGE_FAILED"
            ]
          },
          "param": {
            "type": "string",
            "description": "The persisted request payload, as JSON text."
          },
          "response": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ImageResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present only on SUCCESS."
          },
          "errorCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ImageRecordInfoEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "const": 200
          },
          "msg": {
            "type": "string",
            "const": "success"
          },
          "data": {
            "$ref": "#/components/schemas/ImageRecordInfo"
          }
        }
      },
      "ImageCallbackEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "callbackType": {
                "type": "string",
                "enum": [
                  "complete",
                  "error"
                ]
              },
              "taskId": {
                "type": "string"
              },
              "info": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/ImageResponse"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        }
      },
      "OpenAIImageGenerationRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 32000
          },
          "model": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "n": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "default": 1,
            "description": "Each image is one durable job and occupies one account slot."
          },
          "response_format": {
            "type": "string",
            "enum": [
              "b64_json",
              "url"
            ],
            "default": "b64_json"
          },
          "stream": {
            "type": "boolean",
            "description": "Rejected with 400: streaming is not supported."
          },
          "size": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "quality": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "background": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "output_format": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "output_compression": {
            "type": "integer",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "moderation": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "partial_images": {
            "type": "integer",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "style": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "user": {
            "type": "string",
            "description": "Accepted for OpenAI SDK compatibility; ignored."
          },
          "ratio": {
            "type": "string",
            "enum": [
              "1:1",
              "16:9",
              "9:16"
            ],
            "description": "Output shape. Upstream renders a fixed pixel budget, so only the ratio is honored; it is applied as verified prompt wording. Empty uses the upstream default (16:9-shaped landscape)."
          }
        }
      },
      "OpenAIImage": {
        "type": "object",
        "properties": {
          "b64_json": {
            "type": "string",
            "description": "Base64 image bytes; present when response_format is b64_json."
          },
          "url": {
            "type": "string",
            "description": "Gateway-hosted image URL; present when response_format is url. Valid for `chatgpt.image_retention` (default 7 days); 404 afterwards."
          }
        }
      },
      "OpenAIImagesResponse": {
        "type": "object",
        "properties": {
          "created": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpenAIImage"
            }
          },
          "output_format": {
            "type": "string",
            "enum": [
              "png",
              "webp",
              "jpeg"
            ]
          }
        }
      },
      "OpenAIErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "rate_limit_error",
                  "server_error"
                ]
              },
              "param": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "code": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "ImageJobRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Text prompt for the image. The per-model cap is published by GET /api/v1/images/models (`prompt_limit`; 8000 for Grok Imagine, 32000 elsewhere). A prompt over the cap is always refused, never truncated."
          },
          "model": {
            "type": "string",
            "description": "Model id of the form `provider/model` — `chatgpt/gpt-image-1`, `flow/nano-banana-2-lite|nano-banana-2|nano-banana-pro`, `grok/grok-imagine|grok-imagine-pro` — which is also the whole routing decision. A provider's own bare name is accepted when it is unambiguous. Omitting the field selects `grok/grok-imagine`; naming one explicitly is the deterministic choice. Send `auto` to let the gateway choose the provider: `auto` lets the gateway choose: providers are tried in the order `GET /api/v1/images/models` publishes, and the first that is configured, can honour every field you sent, and has an account free right now takes the job. The response's `model` names what it chose and `requested_model` is `\"auto\"`; pin that id next time for the same renderer. With `drop_unsupported`, a provider that honours everything is preferred, and one that would drop a field is used only when none is free. When nothing can take it the error lists every provider and why: a 503 if any was only busy or not configured, a 400 if every one refused the request itself.",
            "default": "grok/grok-imagine"
          },
          "image_url": {
            "type": "string",
            "description": "Reference image, an http(s) URL. Honoured by chatgpt and flow models; a grok model refuses it — the imagine socket has no property for a reference and nothing in the capture uploads one. For a chatgpt model the reference also determines the output shape, so `aspect_ratio` cannot be combined with it. The URL is fetched through an egress boundary that refuses loopback, private, link-local and metadata addresses, including via DNS and redirects."
          },
          "aspect_ratio": {
            "type": "string",
            "description": "Ratio the model renders, in any spelling that model accepts. Per model: chatgpt `1:1` `16:9` `9:16`; flow `1:1` `3:4` `4:3` `16:9` `9:16` `landscape` `portrait` `square` plus `auto` (requires image_url); grok `1:1` `2:3` `3:2` `3:4` `4:3` `16:9` `9:16` `landscape` `portrait` `square`. GET /api/v1/images/models publishes the authoritative per-model set — it is derived from the same validator this field is checked against. A ratio outside the model's set is refused with the set named, or dropped to the provider default when `drop_unsupported` is set — never silently rendered as a neighbouring ratio."
          },
          "resolution": {
            "type": "string",
            "enum": [
              "1k",
              "2k",
              "4k"
            ],
            "description": "Reserved by the schema. No published model accepts it today — each renders a fixed resolution — so the field is refused, or dropped and reported when `drop_unsupported` is set."
          },
          "negative_prompt": {
            "type": "string",
            "description": "Reserved by the schema. No published model accepts it today; refused, or dropped when `drop_unsupported` is set."
          },
          "drop_unsupported": {
            "type": "boolean",
            "default": false,
            "description": "Turn a capability mismatch from a refusal into a removal. With it set, an optional input the resolved model cannot honour (`image_url`, `resolution`, `negative_prompt`, or an `aspect_ratio` outside the model's set) is dropped and the generation runs without it; a model that does honour the input is unaffected. Dropping is never silent: every removed field is named in the response's `dropped` array, and a dropped `aspect_ratio` leaves the provider's default shape rather than a substituted ratio. A prompt over the model's cap is still refused — truncating a prompt changes the request rather than dropping a config."
          }
        },
        "additionalProperties": false,
        "description": "Unknown fields are rejected. This surface's contract is that an input it cannot honour is named rather than dropped, and the knobs a caller migrating from a provider-native route most plausibly leaves in the body — count, seed, nsfw, watermark, skipUpsampler — are exactly the ones this schema has no home for, so accepting them silently would deliver a different generation than the one requested and say nothing."
      },
      "ImageJob": {
        "type": "object",
        "description": "The one shape every response on this surface decodes into, including request faults: an error carries `status: \"failed\"` and `error_message` instead of a task_id.",
        "properties": {
          "task_id": {
            "type": "string",
            "description": "The durable job id. Poll GET /api/v1/images/jobs/{task_id}; the job keeps running no matter how long the caller stops polling."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "model": {
            "type": "string",
            "description": "The published model id the request resolved to. Present on the submit answer and on a submit failure; the read route does not republish it, because the durable job records the provider's own model name and a Flow job cannot be mapped back to a single published id."
          },
          "requested_model": {
            "type": "string",
            "description": "`auto` when the gateway chose `model`; absent when the request named one."
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImageAsset"
            },
            "description": "Finished images, present once the job succeeds. URLs point at the provider's own unauthenticated file route."
          },
          "dropped": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Request fields removed by `drop_unsupported`, so the accepted request is legible from the answer."
          },
          "error_message": {
            "type": "string"
          }
        }
      },
      "ImageAsset": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          }
        }
      },
      "ImageModelsEnvelope": {
        "type": "object",
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImageModelInfo"
            }
          },
          "auto": {
            "$ref": "#/components/schemas/AutoRouteInfo"
          }
        }
      },
      "ImageModelInfo": {
        "type": "object",
        "description": "The capability table validation enforces: what each published model renders and which optional inputs it honours, so a caller discovers a `4:3` refusal here instead of with a 400.",
        "properties": {
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "enabled": {
            "type": "boolean",
            "description": "False when this gateway is deployed without the provider's queue; submitting that model answers 503."
          },
          "aspect_ratios": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "prompt_limit": {
            "type": "integer"
          },
          "image_url": {
            "type": "boolean"
          },
          "resolution": {
            "type": "boolean"
          },
          "negative_prompt": {
            "type": "boolean"
          }
        }
      },
      "ImageEditRequest": {
        "type": "object",
        "required": [
          "prompt",
          "image_url"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 32000,
            "description": "The edit to apply to the source image."
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "http(s) URL of the source image. Fetched, validated (jpeg/png/webp, at most 20 MiB) and persisted by the gateway before the task is created."
          },
          "model": {
            "type": "string",
            "description": "Accepted for client compatibility; ignored."
          },
          "callBackUrl": {
            "type": "string",
            "description": "Receives one completion notification."
          }
        }
      },
      "FlowImageRef": {
        "description": "One input image. Either a bare Flow media id string, which is what Flow itself uses, or an object naming exactly one of `mediaId`, `url` or `b64`.\n\nPrefer `url` or `b64`. A media id belongs to the Flow project of the account that uploaded it, and this gateway routes across accounts, so an id minted elsewhere will not resolve; bytes are uploaded onto whichever account the task lands on.",
        "oneOf": [
          {
            "type": "string",
            "description": "A Flow media id.",
            "example": "CAMaJDk0YmY5N2Q2LTQ5..."
          },
          {
            "type": "object",
            "properties": {
              "mediaId": {
                "type": "string",
                "description": "An id already in this account's Flow project."
              },
              "url": {
                "type": "string",
                "description": "Public image URL. Fetched and uploaded by the gateway; capped at 12 MiB and must serve an `image/*` content type."
              },
              "b64": {
                "type": "string",
                "description": "Standard base64 image bytes, uploaded by the gateway."
              },
              "crop": {
                "$ref": "#/components/schemas/FlowCrop"
              }
            }
          }
        ]
      },
      "FlowCrop": {
        "type": "object",
        "description": "Normalised crop box (0–1) Flow applies to the reference before generating.",
        "properties": {
          "top": {
            "type": "number"
          },
          "left": {
            "type": "number"
          },
          "bottom": {
            "type": "number"
          },
          "right": {
            "type": "number"
          }
        }
      },
      "FlowImageRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "description": "Supplying any `reference_N` turns generation into an edit: the references steer content and style. Ten slots, read gap-tolerantly — `reference_1` and `reference_3` without a `_2` is two references, not an error.",
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 32000,
            "description": "What to draw. Required."
          },
          "model": {
            "type": "string",
            "enum": [
              "nano-banana-pro",
              "nano-banana-2",
              "nano-banana-2-lite"
            ],
            "default": "nano-banana-pro",
            "description": "Flow's image models. The names do not rank the way the codenames do — this mapping comes from Flow's own model probe, not from guessing."
          },
          "aspectRatio": {
            "type": "string",
            "enum": [
              "landscape",
              "portrait",
              "square",
              "16:9",
              "9:16",
              "1:1",
              "4:3",
              "3:4",
              "auto"
            ],
            "default": "landscape",
            "description": "`auto` takes the shape from the references and so requires at least one; every other value is a fixed shape."
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "default": 1,
            "description": "How many images to generate, each with its own seed. The default is 1 rather than useapi.net's 4 because Flow renders each image as a separate upstream call, and each call needs its own captcha solve - a count of 4 is four solves. All of them are returned in media[]."
          },
          "seed": {
            "type": "integer",
            "minimum": 0,
            "description": "Fixes the generation for reproducibility. Omit for random."
          },
          "replyUrl": {
            "type": "string",
            "description": "Receives one completion notification. Must be an absolute public http(s) URL."
          },
          "reference_1": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_2": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_3": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_4": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_5": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_6": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_7": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_8": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_9": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "reference_10": {
            "$ref": "#/components/schemas/FlowImageRef"
          }
        }
      },
      "FlowVideoRequest": {
        "type": "object",
        "description": "There is no `mode` field. Which of four upstream operations runs follows from the images supplied, the same way Flow's own client decides:\n\n| Supplied | Mode |\n|---|---|\n| nothing | text-to-video |\n| `startImage` | image-to-video |\n| `startImage` + `endImage` | interpolation |\n| `referenceImage_1..7` | ingredients |\n\nFrames and ingredients differ in what the image *is*: a frame appears verbatim in the output, an ingredient only steers content and style. Sending both is refused rather than silently resolved to one. `endImage` without `startImage` is refused too — there would be nothing to interpolate from.\n\nNote the slot names differ from `/flow/images`, which numbers its inputs `reference_N` and allows ten. Using the wrong family on either route is a 400, not a silently dropped image.",
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 32000,
            "description": "Required for text-to-video. Optional once a start frame or ingredients carry the intent."
          },
          "model": {
            "type": "string",
            "enum": [
              "veo-3.1-lite",
              "veo-3.1-fast",
              "veo-3.1-quality",
              "omni-flash"
            ],
            "default": "veo-3.1-fast",
            "description": "Per-model limits are enforced before the task is accepted:\n\n| Model | Input images | Durations | Resolution choice |\n|---|---|---|---|\n| `veo-3.1-lite` | 3 | 4s, 6s, 8s | no |\n| `veo-3.1-fast` | 3 | 4s, 6s, 8s | no |\n| `veo-3.1-quality` | none | 8s | no |\n| `omni-flash` | 7 | 4s, 6s, 8s, 10s | yes |\n\nThe gateway resolves this to whichever concrete catalogue key the routed account is entitled to.\n\n`omni-flash` upstream also accepts a reference *video*, which this gateway does not publish - there is no `referenceVideo` field on this request. Its numbered `referenceImage_N` inputs are images only. If you are porting from an API that offers it, that mode is unavailable here rather than silently ignored."
          },
          "aspectRatio": {
            "type": "string",
            "enum": [
              "landscape",
              "portrait",
              "16:9",
              "9:16"
            ],
            "default": "landscape"
          },
          "duration": {
            "type": "integer",
            "description": "Seconds. Must be one the model offers."
          },
          "resolution": {
            "type": "string",
            "enum": [
              "360p",
              "720p"
            ],
            "description": "Only `omni-flash` exposes this; every veo key fixes its own resolution, and sending it to one is a 400 rather than a silent no-op.\n\n720p is the default because it is the unmarked key: Google publishes a `_360p` variant beside each base key and no `720p` one, so omitting this field and sending `720p` produce the same upstream request. `360p` selects the other key and is cheaper - omni-flash runs 7/10/12/15 credits at 720p for 4/6/8/10s against 4/5/6/7 at 360p.",
            "default": "720p"
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "default": 1,
            "description": "How many videos to generate. Every one is billed: upstream repeats the item inside a single call, so a count of 3 reserves and spends three times the model's price. All of them are returned in media[]."
          },
          "startImage": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "endImage": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_1": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_2": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_3": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_4": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_5": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_6": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "referenceImage_7": {
            "$ref": "#/components/schemas/FlowImageRef"
          },
          "replyUrl": {
            "type": "string",
            "description": "Receives one completion notification."
          }
        }
      },
      "FlowResponse": {
        "type": "object",
        "description": "The generated asset. Present only on `SUCCESS`.",
        "properties": {
          "url": {
            "type": "string",
            "description": "Absolute URL of the generated file when `server.public_base_url` is set, otherwise a path. Files are deleted after `ggflow.file_retention`; this route then answers 404 while the task record stays readable.",
            "example": "https://gateway.example.com/api/v1/flow/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          },
          "mime_type": {
            "type": "string",
            "example": "image/jpeg"
          },
          "bytes": {
            "type": "integer"
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "file_name": {
            "type": "string"
          },
          "media_id": {
            "type": "string",
            "description": "Upstream Flow media id. Usable as a `mediaId` reference only on a task that routes to the same account."
          },
          "project_id": {
            "type": "string",
            "description": "Upstream Flow project the asset lives in."
          },
          "operation_id": {
            "type": "string",
            "description": "Video only: the upstream long-running operation."
          }
        }
      },
      "FlowRecordInfo": {
        "type": "object",
        "properties": {
          "taskId": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "flow_image",
              "flow_video"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "GENERATE_FAILED",
              "CREATE_TASK_FAILED"
            ]
          },
          "response": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FlowResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present only on SUCCESS."
          },
          "errorCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          },
          "media": {
            "type": "array",
            "description": "Every item the task produced, one per requested count. Always present, never null. response is media[0]; it predates count and is kept so existing callers are unaffected. An item refused upstream is absent rather than listed without a url.",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/FlowResponse"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Present only on SUCCESS."
            }
          }
        }
      },
      "FlowRecordInfoEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "const": 200
          },
          "msg": {
            "type": "string",
            "const": "success"
          },
          "data": {
            "$ref": "#/components/schemas/FlowRecordInfo"
          }
        }
      },
      "FlowCallbackEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "description": "200 on completion, 500 on failure."
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "callbackType": {
                "type": "string",
                "enum": [
                  "complete",
                  "error"
                ]
              },
              "taskId": {
                "type": "string"
              },
              "info": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/FlowResponse"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The generated asset on `complete`; null on `error`."
              }
            }
          }
        }
      },
      "GrokImageRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "description": "Text to image. Grok Imagine takes no reference images: the socket accepts a prompt and a set of toggles, and there is no upload path anywhere in its client.",
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 8000,
            "description": "What to draw. Required."
          },
          "model": {
            "type": "string",
            "enum": [
              "grok-imagine",
              "grok-imagine-pro"
            ],
            "default": "grok-imagine",
            "description": "Upstream runs one image model and expresses the quality tier as a flag beside it, so the tier is folded into the name: `grok-imagine` is the fast tier and `grok-imagine-pro` the quality one. The default is the cheap tier because the tiers meter against separate daily allowances, and a caller who omitted the field should not spend the scarcer one."
          },
          "aspectRatio": {
            "type": "string",
            "enum": [
              "landscape",
              "portrait",
              "square",
              "1:1",
              "2:3",
              "3:2",
              "3:4",
              "4:3",
              "9:16",
              "16:9"
            ],
            "default": "2:3",
            "description": "The word forms are accepted so a caller moving over from the Flow routes does not have to relearn the vocabulary. 2:3 is what Grok's own image tab has selected when it opens."
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "default": 1,
            "description": "How many images to render. Unlike Flow, upstream honours this inside one request, so a count of 4 is one generation rather than four. The default is 1 because Grok meters whole requests against a daily allowance and an unasked-for batch is not recoverable."
          },
          "nsfw": {
            "type": "boolean",
            "default": false,
            "description": "Sent as the client's `enable_nsfw` toggle."
          },
          "watermark": {
            "type": "boolean",
            "default": false,
            "description": "Sent as the client's `enable_watermark` toggle."
          },
          "skipUpsampler": {
            "type": "boolean",
            "default": false,
            "description": "Sent as the client's `skip_upsampler` toggle."
          },
          "replyUrl": {
            "type": "string",
            "description": "Receives one completion notification. Must be an absolute public http(s) URL."
          }
        }
      },
      "GrokVideoRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "description": "Text or image to video. One request produces exactly one clip: Grok's video submit carries no count field, so `count` is refused here rather than ignored. Send `imageUrl` to turn the clip into image-to-video.",
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 8000,
            "description": "What to generate. Required."
          },
          "model": {
            "type": "string",
            "enum": [
              "grok-imagine-video"
            ],
            "default": "grok-imagine-video",
            "description": "Grok runs one video model."
          },
          "aspectRatio": {
            "type": "string",
            "enum": [
              "landscape",
              "portrait",
              "square",
              "1:1",
              "2:3",
              "3:2",
              "9:16",
              "16:9"
            ],
            "default": "16:9",
            "description": "Grok's video tab shapes, plus the word forms. An image-only ratio such as 4:3 is refused."
          },
          "duration": {
            "type": "integer",
            "enum": [
              6,
              10,
              15
            ],
            "default": 6,
            "description": "Clip length in seconds: 6, 10 or 15."
          },
          "resolution": {
            "type": "string",
            "enum": [
              "480p",
              "720p"
            ],
            "default": "480p",
            "description": "The two renditions meter against separate daily allowances upstream, so the cheaper one is the default."
          },
          "imageUrl": {
            "type": "string",
            "description": "The clip's first frame, turning the request into image-to-video. The gateway uploads it to the routed account before submitting. Must be an http(s) URL."
          },
          "replyUrl": {
            "type": "string",
            "description": "Receives one completion notification. Must be an absolute public http(s) URL."
          }
        }
      },
      "GrokResponse": {
        "type": "object",
        "description": "One generated asset. Present only on `SUCCESS`.",
        "properties": {
          "url": {
            "type": "string",
            "description": "Absolute URL of the generated file when `server.public_base_url` is set, otherwise a path. Files are deleted after `grok.file_retention`; this route then answers 404 while the task record stays readable.",
            "example": "https://gateway.example.com/api/v1/grok/file/0ba21f5f-2780-4a3a-bdcb-43b7dd401999"
          },
          "mime_type": {
            "type": "string",
            "example": "image/jpeg"
          },
          "bytes": {
            "type": "integer"
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "file_name": {
            "type": "string"
          },
          "asset_id": {
            "type": "string",
            "description": "Upstream Grok image or video id."
          },
          "conversation_id": {
            "type": "string",
            "description": "The Grok conversation the asset belongs to. For video this is the only handle that can recover a generation whose progress stream was cut."
          },
          "source_url": {
            "type": "string",
            "description": "Where the bytes came from upstream, kept for support. Not guaranteed to stay fetchable."
          },
          "status": {
            "type": "string",
            "enum": [
              "succeeded",
              "failed"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Why an item was refused, e.g. `moderated`."
          }
        }
      },
      "GrokRecordInfo": {
        "type": "object",
        "properties": {
          "taskId": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "grok_image",
              "grok_video"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "GENERATE_FAILED",
              "CREATE_TASK_FAILED"
            ]
          },
          "response": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/GrokResponse"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present only on SUCCESS. It is `media[0]`."
          },
          "media": {
            "type": "array",
            "description": "Every item the task delivered. Always present, never null. An item refused by moderation is absent rather than listed without bytes; a task succeeds if any item did, and fails only when every one failed.",
            "items": {
              "$ref": "#/components/schemas/GrokResponse"
            }
          },
          "errorCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "`UPSTREAM_RATE_LIMIT` when the account's daily allowance is spent, `UPSTREAM_AUTH` when the session or the Cloudflare clearance lapsed, `UPSTREAM_REJECTED` for a refused prompt, `CLEARANCE_FAILED` when the browser minter could not be reached, `GROK_TIMEOUT`, `GROK_INPUT_INVALID`."
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "GrokRecordInfoEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "const": 200
          },
          "msg": {
            "type": "string",
            "const": "success"
          },
          "data": {
            "$ref": "#/components/schemas/GrokRecordInfo"
          }
        }
      },
      "GrokCallbackEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "description": "200 on completion, 500 on failure."
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "callbackType": {
                "type": "string",
                "enum": [
                  "complete",
                  "error"
                ]
              },
              "taskId": {
                "type": "string"
              },
              "info": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/GrokResponse"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The first delivered asset on `complete`; null on `error`."
              },
              "media": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/GrokResponse"
                },
                "description": "Every delivered asset. Empty on `error`."
              }
            }
          }
        }
      },
      "VideoJobRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Text prompt for the clip. The per-model cap is published by GET /api/v1/videos/models (`prompt_limit`). A prompt over the cap is always refused, never truncated."
          },
          "model": {
            "type": "string",
            "default": "grok/grok-imagine-video",
            "description": "Model id of the form `provider/model` — `flow/veo-3.1-fast`, `grok/grok-imagine-video`. The provider's own bare name is accepted too. Omitted resolves to the cheapest model, because the expensive mistake here is a silent one. Send `auto` to let the gateway choose the provider: `auto` lets the gateway choose: providers are tried in the order `GET /api/v1/videos/models` publishes, and the first that is configured, can honour every field you sent, and has an account free right now takes the job. The response's `model` names what it chose and `requested_model` is `\"auto\"`; pin that id next time for the same renderer. With `drop_unsupported`, a provider that honours everything is preferred, and one that would drop a field is used only when none is free. When nothing can take it the error lists every provider and why: a 503 if any was only busy or not configured, a 400 if every one refused the request itself."
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "The clip's first frame, turning the request into image-to-video. Flow and Grok video models take one; on Grok the gateway uploads it to the routed account before submitting. A malformed or non-http(s) URL is always refused, never dropped."
          },
          "aspect_ratio": {
            "type": "string",
            "description": "A ratio (`16:9`) or a word (`landscape`). The set differs per provider and is published per model."
          },
          "resolution": {
            "type": "string",
            "description": "Only some models let this be chosen: `flow/omni-flash` offers 360p/720p and `grok/grok-imagine-video` offers 480p/720p, while the veo models render one size each and refuse the field."
          },
          "duration": {
            "type": "integer",
            "description": "Clip length in seconds, from the model's own offered set — Flow's differ per model (4/6/8, or 8 for quality, or 4/6/8/10 for omni-flash) and Grok renders 6. A length outside it is refused rather than rounded, because upstream bills for what it renders."
          },
          "drop_unsupported": {
            "type": "boolean",
            "default": false,
            "description": "Invert the default refusal: an input the model cannot honour is removed and named in `dropped`, and the generation runs without it. Never hides a caller's mistake — a malformed URL, an over-cap prompt and an unknown model stay refusals."
          }
        }
      },
      "VideoAsset": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          }
        }
      },
      "VideoJob": {
        "type": "object",
        "description": "The one shape every response on this surface decodes into, request faults included: an error carries `status: \"failed\"` and `error_message` instead of a task_id.",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "model": {
            "type": "string"
          },
          "requested_model": {
            "type": "string",
            "description": "`auto` when the gateway chose `model`; absent when the request named one."
          },
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VideoAsset"
            }
          },
          "error_message": {
            "type": "string"
          },
          "dropped": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fields drop_unsupported removed. Present only when something was removed, so a removal is never silent."
          }
        }
      },
      "VideoModelInfo": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether this gateway can run the provider at all."
          },
          "aspect_ratios": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "prompt_limit": {
            "type": "integer"
          },
          "image_url": {
            "type": "boolean",
            "description": "Whether a first frame is honoured."
          },
          "durations": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "resolutions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "VideoModelsEnvelope": {
        "type": "object",
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VideoModelInfo"
            }
          },
          "auto": {
            "$ref": "#/components/schemas/AutoRouteInfo"
          }
        }
      },
      "MusicJobRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "The description of the track, or its lyrics when `style` or `title` is given. That change of meaning is Suno's own rule; this surface exposes it through the fields rather than through a mode flag."
          },
          "model": {
            "type": "string",
            "description": "Model id of the form `provider/model`, e.g. `suno/V4_5`. Omitted leaves the choice to the account pool, which knows which models it can currently reach. GET /api/v1/music/models lists them."
          },
          "instrumental": {
            "type": "boolean",
            "description": "Drop the vocal take."
          },
          "style": {
            "type": "string",
            "description": "Musical style. Naming it (or `title`) is what asks for custom mode, and custom mode needs both — the missing one is refused rather than guessed."
          },
          "title": {
            "type": "string",
            "description": "Track title. See `style`."
          },
          "negative_prompt": {
            "type": "string",
            "description": "What to steer away from. Suno calls this negativeTags; it is spelled here the way the image and video schemas spell it."
          },
          "duration": {
            "type": "number",
            "description": "Target length in seconds, honoured where the model supports it."
          }
        }
      },
      "MusicTrack": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "duration": {
            "type": "number"
          },
          "image_url": {
            "type": "string"
          }
        }
      },
      "MusicJob": {
        "type": "object",
        "description": "The one shape every response on this surface decodes into, request faults included.",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "model": {
            "type": "string",
            "description": "Empty when the pool chose, rather than naming a model this surface did not pick."
          },
          "tracks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MusicTrack"
            },
            "description": "One submit yields two takes. The first appears while the second is still rendering, so a pending job can already carry a playable track."
          },
          "error_message": {
            "type": "string"
          },
          "credits_used": {
            "type": "integer",
            "minimum": 0,
            "description": "Suno upstream credits this task spent: the configured per-kind cost, applied when the paid request is sent (Suno reports no per-task cost). Present once known; `0` for a task that ended before the paid boundary; absent while a task has not spent yet. A recovered resubmission adds its own cost."
          }
        }
      },
      "MusicModelInfo": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether any usable account can currently serve it. Unlike images and video this is not a build-time switch — Suno's menu is the account pool's."
          },
          "prompt_limit": {
            "type": "integer"
          },
          "accounts": {
            "type": "integer",
            "description": "How many upstream accounts can serve it right now."
          }
        }
      },
      "MusicModelsEnvelope": {
        "type": "object",
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MusicModelInfo"
            }
          }
        }
      },
      "Job": {
        "type": "object",
        "description": "The medium-agnostic job shape. `medium` says which result list to read; the others are absent rather than empty.",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "medium": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "music"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "model": {
            "type": "string"
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImageAsset"
            }
          },
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VideoAsset"
            }
          },
          "tracks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MusicTrack"
            }
          },
          "error_message": {
            "type": "string"
          },
          "credits_used": {
            "type": "integer",
            "minimum": 0,
            "description": "Music jobs only. Suno upstream credits this task spent: the configured per-kind cost, applied when the paid request is sent (Suno reports no per-task cost). Present once known; `0` for a task that ended before the paid boundary; absent while a task has not spent yet. A recovered resubmission adds its own cost."
          }
        }
      },
      "AutoRouteInfo": {
        "type": "object",
        "description": "What `model: \"auto\"` will do on this gateway. Not a model row: its capabilities are whichever provider a request resolves to.",
        "properties": {
          "model": {
            "type": "string",
            "const": "auto"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether any provider behind `auto` is configured on this gateway."
          },
          "preference": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Provider ids in the order `auto` tries them: the operator's configured order, then any provider it omits."
          }
        }
      },
      "CreditEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "integer",
            "description": "Cached upstream credits across operator-enabled Suno accounts."
          }
        }
      },
      "CreditUsageEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer"
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "format": "date-time"
              },
              "to": {
                "type": "string",
                "format": "date-time"
              },
              "credits": {
                "type": "integer",
                "description": "Credits spent by tasks the gateway saw accepted or finished."
              },
              "unconfirmedCredits": {
                "type": "integer",
                "description": "Credits applied pessimistically to `SUBMISSION_UNKNOWN` tasks, which Suno probably charged."
              },
              "jobs": {
                "type": "integer"
              },
              "kinds": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Credits per job kind, both buckets included."
              }
            }
          }
        }
      }
    }
  }
}
