Skip to content

Instantly share code, notes, and snippets.

@alexsanqp
Last active July 13, 2025 20:20
Show Gist options
  • Select an option

  • Save alexsanqp/5b9e848b73f9ce5086ee7369482fb091 to your computer and use it in GitHub Desktop.

Select an option

Save alexsanqp/5b9e848b73f9ce5086ee7369482fb091 to your computer and use it in GitHub Desktop.
This script provides a simple, dependency-light Python client to interact directly with the unofficial Suno AI API. It allows you to programmatically generate music, poll for the completion status of your tracks, and download the final audio files. #russiaterroriststate #sunoai #sunoapi
import requests
import time
import os
import json
from typing import List, Dict, Any
from dotenv import load_dotenv
class ApiConfig:
BASE_URL = "https://studio-api.prod.suno.com"
GENERATE_ENDPOINT = "/api/generate/v2-web/"
FEED_ENDPOINT = "/api/feed/v2"
class Track:
def __init__(self, data: Dict[str, Any]):
self.id = data.get("id")
self.status = data.get("status")
self.title = data.get("title")
self.audio_url = data.get("audio_url")
def __repr__(self) -> str:
return f"Track(id='{self.id}', title='{self.title}', status='{self.status}')"
class SunoClient:
def __init__(self, bearer_token: str):
if not bearer_token or not bearer_token.startswith("Bearer "):
raise ValueError("A valid Bearer token must be provided.")
self.session = requests.Session()
self.session.headers.update({
"Accept": "*/*",
"Authorization": bearer_token,
})
self.api_config = ApiConfig()
def _request(self, method: str, endpoint: str, **kwargs) -> Any:
url = self.api_config.BASE_URL + endpoint
try:
response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response.json() if response.content else None
except requests.exceptions.HTTPError as http_err:
print(f"HTTP Error: {http_err}\nServer response: {http_err.response.text}")
raise
except requests.exceptions.RequestException as req_err:
print(f"Connection Error: {req_err}")
raise
def create_track(self, title: str, prompt: str, tags: str, model: str = "chirp-v3-5") -> List[str]:
print(f"Sending request to create track '{title}'...")
payload = {
"prompt": prompt, "tags": tags, "title": title, "mv": model,
"continue_clip_id": None, "continue_at": 0, "generation_type": "TEXT", "task": None
}
headers = self.session.headers.copy()
headers['Content-Type'] = 'text/plain;charset=UTF-8'
response_data = self._request(
"POST", self.api_config.GENERATE_ENDPOINT, data=json.dumps(payload), headers=headers
)
clip_ids = [clip['id'] for clip in response_data.get('clips', [])]
print(f"Request accepted. Generated clips with IDs: {clip_ids}")
return clip_ids
def get_tracks(self, ids: List[str]) -> List[Track]:
if not ids:
return []
id_string = ",".join(ids)
response_data = self._request("GET", self.api_config.FEED_ENDPOINT, params={"ids": id_string})
clips_data = response_data.get('clips', [])
return [Track(item) for item in clips_data if isinstance(item, dict)]
def poll_for_completion(self, ids: List[str], poll_interval: int = 5, timeout: int = 300) -> List[Track]:
print(f"Waiting for generation to complete for IDs: {ids}...")
start_time = time.time()
while time.time() - start_time < timeout:
tracks = self.get_tracks(ids)
completed_tracks = [t for t in tracks if t.status == 'complete']
print(f"Status: {len(completed_tracks)}/{len(ids)} tracks ready.")
if len(completed_tracks) == len(ids):
print("All tracks generated successfully!")
return completed_tracks
time.sleep(poll_interval)
raise TimeoutError("Track generation timeout exceeded.")
def download(self, track: Track, save_dir: str = 'suno_downloads'):
if not track.audio_url:
print(f"Error: track '{track.title}' has no download URL.")
return
os.makedirs(save_dir, exist_ok=True)
safe_title = "".join(c for c in track.title if c.isalnum() or c in (' ', '_')).rstrip()
filename = f"{safe_title}_{track.id[:8]}.mp3"
filepath = os.path.join(save_dir, filename)
print(f"Downloading '{track.title}' to file {filepath}...")
try:
response = requests.get(track.audio_url, stream=True)
response.raise_for_status()
with open(filepath, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print("Download complete.")
except requests.exceptions.RequestException as e:
print(f"Failed to download file: {e}")
if __name__ == '__main__':
load_dotenv()
BEARER_TOKEN = os.getenv("BEARER_TOKEN") # BEARER_TOKEN="Bearer eyJhbGciOiJSUzI1NiIsIm..."
try:
client = SunoClient(bearer_token=BEARER_TOKEN)
song_title = "Вечірній Київ"
song_prompt = """
[Verse 1]
Сирени знов розрізали світанок
І дим закрив собою мирний ранок
Сусід прийшов з війною, не з добром
Лишаючи лиш попіл за вікном
[Chorus]
Імперія брехні, держава-терорист
Твій кожен постріл – це кривавий, чорний лист
Та не зламати дух, що прагне до життя
Ми будем вільні! Без тебе. Без сміття.
"""
song_style = "Ukrainian patriotic rock, epic rock ballad, powerful"
track_ids = client.create_track(
title=song_title, prompt=song_prompt, tags=song_style
)
if not track_ids:
raise Exception("Failed to create tracks.")
completed_tracks = client.poll_for_completion(track_ids)
print("\n--- Downloading finished tracks ---")
for t in completed_tracks:
client.download(t)
except (ValueError, requests.HTTPError, TimeoutError) as e:
print(f"\nExecution interrupted due to an error: {e}")
except Exception as e:
print(f"\nAn unexpected error occurred: {e}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment