Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ We have deployed an example bound to a free Suno account, so it has daily usage
- Perfectly implements the creation API from suno.ai.
- Automatically keep the account active.
- Solve CAPTCHAs automatically using [2Captcha](https://2captcha.com) and [Playwright](https://playwright.dev) with [rebrowser-patches](https://github.com/rebrowser/rebrowser-patches).
- Compatible with the format of OpenAIs `/v1/chat/completions` API.
- Compatible with the format of OpenAI's `/v1/chat/completions` API.
- Supports Custom Mode.
- One-click deployment to [Vercel](#deploy-to-vercel) & [Docker](#docker).
- In addition to the standard API, it also adapts to the API Schema of Agent platforms like GPTs and Coze, so you can use it as a tool/plugin/Action for LLMs and integrate it into any AI Agent.
Expand Down Expand Up @@ -99,7 +99,7 @@ docker compose build && docker compose up

- If deployed to Vercel, please add the environment variables in the Vercel dashboard.

- If youre running this locally, be sure to add the following to your `.env` file:
- If you're running this locally, be sure to add the following to your `.env` file:
#### Environment variables
- `SUNO_COOKIE` — the `Cookie` header you obtained in the first step.
- `TWOCAPTCHA_KEY` — your 2Captcha API key from the second step.
Expand All @@ -118,7 +118,7 @@ BROWSER_HEADLESS=true

### 5. Run suno-api

- If youve deployed to Vercel:
- If you've deployed to Vercel:
- Please click on Deploy in the Vercel dashboard and wait for the deployment to be successful.
- Visit the `https://<vercel-assigned-domain>/api/get_limit` API for testing.
- If running locally:
Expand Down Expand Up @@ -148,17 +148,19 @@ Suno API currently mainly implements the following APIs:

```bash
- `/api/generate`: Generate music
- `/v1/chat/completions`: Generate music - Call the generate API in a format that works with OpenAIs API.
- `/v1/chat/completions`: Generate music - Call the generate API in a format that works with OpenAI's API.
- `/api/custom_generate`: Generate music (Custom Mode, support setting lyrics, music style, title, etc.)
- `/api/generate_lyrics`: Generate lyrics based on prompt
- `/api/get`: Get music information based on the id. Use “,” to separate multiple ids.
- `/api/get`: Get music information based on the id. Use "," to separate multiple ids.
If no IDs are provided, all music will be returned.
- `/api/get_limit`: Get quota Info
- `/api/extend_audio`: Extend audio length
- `/api/generate_stems`: Make stem tracks (separate audio and music track)
- `/api/get_aligned_lyrics`: Get list of timestamps for each word in the lyrics
- `/api/clip`: Get clip information based on ID passed as query parameter `id`
- `/api/concat`: Generate the whole song from extensions
- `/api/projects`: Get a list of projects
- `/api/projects/{id}`: Get a specific project by ID with options to hide disliked content
```

You can also specify the cookies in the `Cookie` header of your request, overriding the default cookies in the `SUNO_COOKIE` environment variable. This comes in handy when, for example, you want to use multiple free accounts at the same time.
Expand Down Expand Up @@ -217,6 +219,16 @@ def generate_whole_song(clip_id):
response = requests.post(url, json=payload)
return response.json()

def get_projects(page=1):
url = f"{base_url}/api/projects?page={page}"
response = requests.get(url)
return response.json()

def get_project(project_id, hide_disliked=False, page=1):
url = f"{base_url}/api/projects/{project_id}?hide_disliked={str(hide_disliked).lower()}&page={page}"
response = requests.get(url)
return response.json()


if __name__ == '__main__':
data = generate_audio_by_prompt({
Expand Down Expand Up @@ -289,6 +301,18 @@ async function getClipInformation(clipId) {
return response.data;
}

async function getProjects(page = 1) {
const url = `${baseUrl}/api/projects?page=${page}`;
const response = await axios.get(url);
return response.data;
}

async function getProject(projectId, hideDisliked = false, page = 1) {
const url = `${baseUrl}/api/projects/${projectId}?hide_disliked=${hideDisliked}&page=${page}`;
const response = await axios.get(url);
return response.data;
}

async function main() {
const data = await generateAudioByPrompt({
prompt:
Expand Down
60 changes: 60 additions & 0 deletions src/app/api/projects/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { NextResponse, NextRequest } from 'next/server';
import { cookies } from 'next/headers';
import { sunoApi } from '@/lib/SunoApi';
import { corsHeaders } from '@/lib/utils';

export const dynamic = 'force-dynamic';

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
if (req.method === 'GET') {
try {
const projectId = params.id;
const url = new URL(req.url);
const hideDisliked = url.searchParams.get('hide_disliked') === 'true';
const page = url.searchParams.get('page') || '1';
const cookie = (await cookies()).toString();

const data = await (await sunoApi(cookie)).getProject(
projectId,
hideDisliked,
parseInt(page)
);

return new NextResponse(JSON.stringify(data), {
status: 200,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
} catch (error) {
console.error('Error fetching project:', error);

return new NextResponse(
JSON.stringify({ error: 'Internal server error' }),
{
status: 500,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
}
);
}
} else {
return new NextResponse('Method Not Allowed', {
headers: {
Allow: 'GET',
...corsHeaders
},
status: 405
});
}
}

export async function OPTIONS(request: Request) {
return new Response(null, {
status: 200,
headers: corsHeaders
});
}
54 changes: 54 additions & 0 deletions src/app/api/projects/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextResponse, NextRequest } from 'next/server';
import { cookies } from 'next/headers';
import { sunoApi } from '@/lib/SunoApi';
import { corsHeaders } from '@/lib/utils';

export const dynamic = 'force-dynamic';

export async function GET(req: NextRequest) {
if (req.method === 'GET') {
try {
const url = new URL(req.url);
const page = url.searchParams.get('page') || '1';
const cookie = (await cookies()).toString();

const data = await (await sunoApi(cookie)).projects(parseInt(page));

return new NextResponse(JSON.stringify(data), {
status: 200,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
} catch (error) {
console.error('Error fetching projects:', error);

return new NextResponse(
JSON.stringify({ error: 'Internal server error' }),
{
status: 500,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
}
);
}
} else {
return new NextResponse('Method Not Allowed', {
headers: {
Allow: 'GET',
...corsHeaders
},
status: 405
});
}
}

export async function OPTIONS(request: Request) {
return new Response(null, {
status: 200,
headers: corsHeaders
});
}
189 changes: 189 additions & 0 deletions src/app/docs/swagger-suno-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,195 @@
}
}
},
"/api/projects": {
"get": {
"summary": "Get user's projects",
"description": "Retrieves the user's projects from Suno API.",
"tags": ["default"],
"parameters": [
{
"in": "query",
"name": "page",
"description": "Page number (defaults to 1)",
"required": false,
"schema": {
"type": "number",
"default": 1
}
}
],
"responses": {
"200": {
"description": "success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"projects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Project ID"
},
"name": {
"type": "string",
"description": "Project name"
},
"created_at": {
"type": "string",
"description": "Creation date and time"
},
"last_modified": {
"type": "string",
"description": "Last modification date and time"
}
}
}
},
"total_results": {
"type": "integer",
"description": "Total number of projects"
},
"current_page": {
"type": "integer",
"description": "Current page number"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Internal server error"
}
}
}
}
}
}
}
}
},
"/api/projects/{id}": {
"get": {
"summary": "Get project by ID",
"description": "Retrieves a specific project by ID from Suno API.",
"tags": ["default"],
"parameters": [
{
"in": "path",
"name": "id",
"description": "Project ID",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "hide_disliked",
"description": "Whether to hide disliked content (defaults to false)",
"required": false,
"schema": {
"type": "boolean",
"default": false
}
},
{
"in": "query",
"name": "page",
"description": "Page number (defaults to 1)",
"required": false,
"schema": {
"type": "number",
"default": 1
}
}
],
"responses": {
"200": {
"description": "success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"project": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Project ID"
},
"name": {
"type": "string",
"description": "Project name"
},
"description": {
"type": "string",
"description": "Project description"
},
"created_at": {
"type": "string",
"description": "Creation date and time"
},
"last_modified": {
"type": "string",
"description": "Last modification date and time"
},
"clips": {
"type": "array",
"items": {
"type": "object",
"description": "Clip information"
}
}
}
},
"total_results": {
"type": "integer",
"description": "Total number of clips in the project"
},
"current_page": {
"type": "integer",
"description": "Current page number"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Internal server error"
}
}
}
}
}
}
}
}
},
"/api/get_limit": {
"get": {
"summary": "Get quota information.",
Expand Down
Loading