# Suno API Integration Guide

This repository contains a simple Python FastAPI server to demonstrate how to integrate with the Suno API using OAuth 2.0.

## Part 1: Authentication with OAuth 2.0

To integrate with the Suno API, your application must first get permission from users to access their account. This is done using the industry-standard **OAuth 2.0 Authorization Code Flow with Proof Key for Code Exchange (PKCE)**.

The flow is as follows:

1.  Your user initiates the connection from your application.
2.  You redirect the user to the Suno authorization endpoint with specific parameters.
3.  The user logs into Suno (if not already) and approves your application's request for permissions.
4.  Suno redirects the user back to your pre-configured `redirect_uri` with an `authorization_code`.
5.  Your server exchanges this `code` (along with a `code_verifier`) for an `access_token` and `refresh_token`.
6.  You store these tokens securely and use the `access_token` to make authenticated API calls to Suno on behalf of the user.

---

### 1.1. Endpoints & Credentials

You will be provided with a unique `Client ID` and `Client Secret`. The `Client Secret` must be stored securely on your backend and never exposed in client-side code.

| Parameter | Value |
| :--- | :--- |
| **Authorization URL** | `https://studio-api.prod.suno.com/api/v2/external/oauth/authorize/` |
| **Token URL** | `https://studio-api.prod.suno.com/api/v2/external/oauth/token/` |
| **Registered Redirect URIs** | `http://localhost:3000/oauth/suno/callback`, etc. |
| **Available Scopes** | `read_profile generate_music read_music` |

---

### 1.2. Authorization Flow (Step-by-Step)

#### Step 1: Construct the Authorization URL & Redirect User

To begin, generate a `code_verifier` and `code_challenge` for PKCE.

*   **`code_verifier`**: A cryptographically random string (e.g., 43-128 characters). **Store this temporarily** on your server, associated with the user's session.
*   **`code_challenge`**: The BASE64URL-encoded SHA256 hash of the `code_verifier`.

Next, construct the authorization URL and redirect the user's browser to it.

**Method:** `GET`
**URL:** `https://studio-api.prod.suno.com/api/v2/external/oauth/authorize/`

**Query Parameters:**

| Parameter | Requirement | Description |
| :--- | :--- | :--- |
| `response_type` | **Required** | Must be `code`. |
| `client_id` | **Required** | Your unique Client ID. |
| `redirect_uri` | **Required** | The absolute URI where the user is sent after authorization. **Must** be one of your registered URIs. |
| `scope` | **Required** | A space-separated list of scopes you are requesting (e.g., `read_profile generate_music`). |
| `code_challenge` | **Required** | The PKCE code challenge you generated. |
| `code_challenge_method` | **Required** | Must be `S256`. |
| `state` | **Recommended** | A random string generated by you to prevent CSRF attacks. Store it in the user's session to verify in Step 2. |

#### Step 2: Handle the Callback from Suno

After the user approves, Suno will redirect them to your `redirect_uri` with the following query parameters:

| Parameter | Description |
| :--- | :--- |
| `code` | The single-use authorization code. Expires shortly. |
| `state` | The same value you provided in Step 1. Your server must verify this matches the value stored in the session. |

#### Step 3: Exchange Authorization Code for Tokens

Your backend server must now make a `POST` request to the Suno Token URL to exchange the `code` for tokens.

**Method:** `POST`
**URL:** `https://studio-api.prod.suno.com/api/v2/external/oauth/token/`
**Authentication:** `HTTP Basic`. The username is your `Client ID`, the password is your `Client Secret`.
**Headers:**

*   `Authorization: Basic <base64(client_id:client_secret)>`
*   `Content-Type: application/x-www-form-urlencoded`

**Body Parameters (form-urlencoded):**

| Parameter | Requirement | Description |
| :--- | :--- | :--- |
| `grant_type` | **Required** | Must be `authorization_code`. |
| `code` | **Required** | The `authorization_code` received in Step 2. |
| `redirect_uri` | **Required** | The exact same `redirect_uri` used in Step 1. |
| `code_verifier` | **Required** | The original `code_verifier` string you generated and stored in Step 1. |

#### Step 4: Receive and Store Tokens

The response to a successful token exchange will be a JSON object.

**Example Response:**

```json
{
    "access_token": "...",
    "expires_in": 3600,
    "token_type": "Bearer",
    "scope": "read_profile generate_music",
    "refresh_token": "..."
}
```

**Action:** Securely store the `access_token` and `refresh_token` in your database, associated with your user's account. The `access_token` is short-lived.

---

### 1.3. Using and Refreshing Tokens

#### Making Authenticated API Calls

To call a protected Suno API endpoint (e.g., `/api/v2/external/oauth/generate`), include the `access_token` in the `Authorization` header.

`Authorization: Bearer <access_token>`

#### Refreshing the Access Token

When the `access_token` expires, use the `refresh_token` to get a new one without requiring user interaction.

**Method:** `POST`
**URL:** `https://studio-api.prod.suno.com/api/v2/external/oauth/token/`
**Authentication:** `HTTP Basic` (same as Step 3).
**Headers:**

*   `Authorization: Basic <base64(client_id:client_secret)>`
*   `Content-Type: application/x-www-form-urlencoded`

**Body Parameters (form-urlencoded):**

| Parameter | Requirement | Description |
| :--- | :--- | :--- |
| `grant_type` | **Required** | Must be `refresh_token`. |
| `refresh_token` | **Required** | The `refresh_token` you received and stored. |

The response will be a new set of tokens. Securely store the new `access_token` and potentially the new `refresh_token` (as it may be rotated).

---

## Part 2: Example Implementation (Python/FastAPI)

This section provides instructions on how to set up and run the example FastAPI application included in this repository. The code itself is in the `main.py` file and the `templates/` directory.

### Setup

1.  **Create and activate a virtual environment using `uv`:**

    First, install `uv` if you don't have it. On macOS and Linux:
    ```bash
    curl -LsSf https://astral.sh/uv/install.sh | sh
    ```
    On Windows (in PowerShell):
    ```bash
    powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
    ```

    Now, create and activate the virtual environment:
    ```bash
    # Create a virtual environment in a .venv directory
    uv venv

    # Activate the virtual environment
    # On macOS/Linux:
    source .venv/bin/activate
    # On Windows PowerShell:
    .venv\Scripts\Activate.ps1
    ```

2.  **Install dependencies:**
    With the virtual environment activated, install the required packages.
    ```bash
    uv pip install "fastapi[all]" httpx "jinja2" "python-jose[cryptography]" "python-dotenv"
    ```

3.  **Configure your credentials:**
    Create a file named `.env` in the root of the project directory. This file will store your client credentials securely. Add the following lines to it, replacing the placeholder values with your actual `Client ID` and `Client Secret`.

    ```env
    CLIENT_ID="your-suno-client-id"
    CLIENT_SECRET="your-suno-client-secret"
    ```
    The application will load these variables at startup.

### Running the Example

1.  Run the application server from your terminal:
    ```bash
    uvicorn main:app --reload --port 3000
    ```
    If your virtual environment is not activated, you can use `uv run`:
    ```bash
    uv run uvicorn main:app --reload --port 3000
    ```

2.  Open your web browser and navigate to `http://localhost:3000`.

3.  Click the "Connect with Suno" button and follow the authorization flow to link your account. After success, you will be redirected to a page where you can see your stored tokens and try making API calls.

---

## Part 3: API Reference - Music Generation

Once you have an `access_token`, you can use it to make authenticated API calls. This reference details how to generate music. The API follows a simple, asynchronous workflow: you submit a generation request and then poll for the results.

All API requests to protected endpoints must include an `Authorization` header with the `access_token` you obtained in Part 1.

`Authorization: Bearer <YOUR_ACCESS_TOKEN>`

Requests without a valid token will result in a `401 Unauthorized` error.

---

### 3.1. Generate a Song

To create a new song, send a `POST` request to the generation endpoint. This will queue a generation task and immediately return one or more clip objects with a unique `id` for each. You will use these IDs to poll for the final results.

#### Endpoint

`POST /api/v2/external/oauth/generate`

#### Request Body

The request body is a JSON object based on the following specifications:

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| `topic` | `string` | Yes | | A detailed description of the song you want to create, which is used to generate the title and lyrics. E.g., "A high-energy 80s rock anthem about winning the world championship." |
| `tags` | `string` | No | Generated from `topic` | The style of music, including genres, instruments, and mood. E.g., "80s rock, synth, electric guitar, triumphant". |
| `prompt` | `string` | No | Generated from `topic` | Custom lyrics for the song. If provided, `topic` will only be used for the title. Must be formatted with section markers, e.g., `[Verse] ... [Chorus] ...` |
| `make_instrumental` | `boolean` | No | `false` | If `true`, generates an instrumental song without vocals. |
| `model` | `string` | No | `chirp-v4` | The model to use for generation. |
| `cover_clip_id` | `string` | No | `null` | The ID of a base clip to use when creating a cover of a song in a different style. |
| `negative_tags` | `string` | No | `null` | Styles or tags to avoid during generation. |
| `temp_semantic` | `float` | No | `null` | Controls the creativity of the generation. Higher values lead to more surprising results. |
| `early_callback` | `boolean` | No | `false` | If `true`, the service may call back before generation is fully complete. |
| `extra` | `object` | No | `{}` | A dictionary for any extra parameters. |

#### Example Requests

**Minimal example (let Suno generate lyrics and style):**
```json
{
    "topic": "A soulful jazzy blues ballad about a long-lost love found again on a rainy day in New Orleans."
}
```

**Manual style tags (let Suno write lyrics):**
```json
{
    "topic": "A soulful ballad about a long-lost love found again on a rainy day in New Orleans.",
    "tags": "soul, ballad, piano, saxophone, slow"
}
```

**Custom lyrics and style:**
```json
{
    "topic": "Rainy Day Reunion",
    "prompt": "[Intro]\\n On this rainy day\\nI found my long lost love\\nin New Orleans\\n[Outro]",
    "tags": "soulful, evolving jazzy chord progression, expressive ballad, laid-back smooth rhodes piano, muted saxophone, slow",
    "make_instrumental": false
}
```

#### Response (On Success)

**Status:** `200 OK`

Returns an array of clip objects. At this stage, the status will be `submitted`, and most fields will be `null` until generation completes.

```json
[
    {
        "id": "e4ed728b-7033-4d7c-87c2-a0b635791c53",
        "request_id": "a9e6129f-2117-457c-933e-e2c710d2906b",
        "video_url": null,
        "audio_url": null,
        "image_url": null,
        "image_large_url": null,
        "created_at": "2025-01-15T18:30:00.000Z",
        "status": "submitted",
        "title": null,
        "metadata": {
            "tags": null,
            "prompt": null,
            "gpt_description_prompt": "A soulful ballad about a long-lost love found again on a rainy day in New Orleans.",
            "type": "gen",
            "duration": null,
            "error_type": null,
            "error_message": null
        }
    }
]
```

---

### 3.2. Get Generation Status and Results

After submitting a generation request, you must poll this endpoint to check the status and retrieve the final song assets. We recommend polling every 5-10 seconds.

#### Endpoint

`GET /api/v2/external/oauth/clips/`

#### Query Parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `ids` | `string` | **Yes** | A comma-separated list of clip `id`s to fetch. Maximum of 10 IDs per request. |

#### Response (On Success)

**Status:** `200 OK`

Returns an array of the requested clip objects with their current status and data. The order is not guaranteed. Once a clip's `status` is `complete`, the `audio_url`, `video_url`, and other metadata will be populated.

**Example Response (for a completed clip):**
```json
[
    {
        "id": "e4ed728b-7033-4d7c-87c2-a0b635791c53",
        "request_id": "a9e6129f-2117-457c-933e-e2c710d2906b",
        "video_url": "https://cdn1.suno.ai/e4ed728b-7033-4d7c-87c2-a0b635791c53.mp4",
        "audio_url": "https://cdn1.suno.ai/e4ed728b-7033-4d7c-87c2-a0b635791c53.mp3",
        "image_url": "https://cdn1.suno.ai/image_e4ed728b-7033-4d7c-87c2-a0b635791c53.jpeg",
        "image_large_url": "https://cdn1.suno.ai/image_large_e4ed728b-7033-4d7c-87c2-a0b635791c53.jpeg",
        "created_at": "2025-01-15T18:30:00.000Z",
        "status": "complete",
        "title": "Rainy Day Reunion",
        "metadata": {
            "tags": "soul, ballad, piano, saxophone, slow",
            "prompt": "[Verse]\\nRaindrops on the window pane\\nFamiliar rhythm, sweet refrain\\nDown on Bourbon Street I roam\\nA lonely feeling, far from home\\n\\n[Chorus]\\nThen I saw your face in the crowd\\nA whisper turning to a sound so loud\\nYears just melted in the pourin' rain\\nMy long-lost love, found again",
            "gpt_description_prompt": "A soulful ballad about a long-lost love found again on a rainy day in New Orleans.",
            "type": "gen",
            "duration": 58.5,
            "error_type": null,
            "error_message": null
        }
    }
]
```
---

### 3.3. API Object Schemas

#### The Clip Object

The `Clip` object contains all information about a generated song.

| Field | Type | Description |
| :--- | :--- | :--- |
| `id` | `string (uuid)` | The unique identifier for the clip. |
| `request_id` | `string (uuid)` | The ID of the generation request batch this clip belongs to. |
| `status` | `string` | The current status of the generation. See **Status Values** below. |
| `title` | `string` | The title of the song. Populated when generation is complete. |
| `image_url` | `string` | URL for the standard-resolution cover art image (`1024x1024` JPEG). |
| `image_large_url` | `string` | URL for the high-resolution cover art image (`2048x2048` JPEG). |
| `audio_url` | `string` | URL for the generated audio file (MP3). |
| `video_url` | `string` | URL for the generated video file (MP4), which contains the audio and cover art. |
| `created_at` | `string (datetime)` | The ISO 8601 timestamp of when the generation request was created. |
| `metadata` | `object` | An object containing additional details about the generation. See **Metadata Object** below. |

#### The Metadata Object

| Field | Type | Description |
| :--- | :--- | :--- |
| `tags` | `string` | The final style tags used for generation. |
| `prompt` | `string` | The full lyrics of the song. |
| `gpt_description_prompt` | `string` | The original `topic` from the generation request. |
| `type` | `string` | The type of generation (e.g., `gen`). |
| `duration` | `float` | The duration of the audio in seconds. |
| `error_type` | `string` | If an error occurred, a code indicating the type of error (e.g., `moderation_error`). |
| `error_message` | `string` | A user-facing message describing the error. |


#### Status Values

| Status | Description |
| :--- | :--- |
| `submitted` | The request has been received and is waiting to enter the queue. |
| `queued` | The request is in the queue to be processed by a generation model. |
| `streaming` | The song is actively being generated. Audio/video URLs may become available for partial streaming. |
| `complete` | The generation is finished. All assets and metadata are final and available. |
| `error` | The generation failed. Check `metadata.error_type` and `metadata.error_message` for details. |
