Skip to content

Commit 610adc5

Browse files
CodeKeanuclaude
andcommitted
fix: WAV conversion requires BOTH task_id AND audio_id
BREAKING CHANGE: convert_to_wav() now requires both parameters Root cause: Suno API documentation states that BOTH taskId AND audioId are required parameters, not either/or as previously implemented. Changes: - suno_client.py: Made task_id and audio_id required (not optional) - suno_client.py: Updated validation to check both IDs are provided - suno_client.py: Always send both IDs in API payload - suno_client.py: Updated docstring with correct usage example - server.py: Updated MCP tool schema to require both IDs - server.py: Updated handler validation for both required IDs - test_wav_feature.py: Updated validation tests for new signature Tested with real track IDs - conversion now works successfully! Result: {'code': 200, 'msg': 'success', 'data': {'taskId': '...'}} 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d7e0b42 commit 610adc5

4 files changed

Lines changed: 52 additions & 182 deletions

File tree

server.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ async def handle_list_tools() -> list[Tool]:
150150
),
151151
Tool(
152152
name="convert_to_wav",
153-
description="Convert a generated audio track to WAV format. Provide EITHER task_id OR audio_id (or both). Using audio_id is recommended for precision when a generation task has multiple tracks. Returns a conversion task ID that can be used to check conversion status.",
153+
description="Convert a generated audio track to WAV format. Requires BOTH task_id (generation job ID) AND audio_id (specific track ID). The task_id identifies the generation job, and audio_id specifies which track to convert. Returns a conversion task ID that can be used to check conversion status.",
154154
inputSchema={
155155
"type": "object",
156156
"properties": {
@@ -160,14 +160,14 @@ async def handle_list_tools() -> list[Tool]:
160160
},
161161
"task_id": {
162162
"type": "string",
163-
"description": "Original generation task ID (taskId) from generate_music - converts all tracks from that generation (optional if audio_id provided)"
163+
"description": "Generation task ID (taskId) from music['data']['taskId'] - the hex string identifying the generation job (REQUIRED)"
164164
},
165165
"audio_id": {
166166
"type": "string",
167-
"description": "Track ID (audioId) of the specific generated music track to convert - more precise, recommended (optional if task_id provided)"
167+
"description": "Track ID (audioId) from music['data']['sunoData'][0]['id'] - UUID identifying the specific track to convert (REQUIRED)"
168168
}
169169
},
170-
"required": ["callback_url"]
170+
"required": ["callback_url", "task_id", "audio_id"]
171171
}
172172
),
173173
Tool(
@@ -414,10 +414,13 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[TextConten
414414
if not callback_url:
415415
raise ValueError("callback_url is required")
416416

417-
if not task_id and not audio_id:
418-
raise ValueError("Either task_id or audio_id must be provided")
417+
if not task_id:
418+
raise ValueError("task_id is required (generation job ID from music['data']['taskId'])")
419+
420+
if not audio_id:
421+
raise ValueError("audio_id is required (track ID from music['data']['sunoData'][0]['id'])")
419422

420-
# Convert to WAV (pass whichever IDs were provided)
423+
# Convert to WAV (BOTH IDs required per Suno API)
421424
result = await suno_client.convert_to_wav(
422425
callback_url=callback_url,
423426
task_id=task_id,

suno_client.py

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -243,48 +243,50 @@ async def get_credits(self) -> Dict[str, Any]:
243243
async def convert_to_wav(
244244
self,
245245
callback_url: str,
246-
task_id: str = None,
247-
audio_id: str = None
246+
task_id: str,
247+
audio_id: str
248248
) -> Dict[str, Any]:
249249
"""
250250
Convert a generated audio track to WAV format.
251251
252-
Per Suno API docs: You can provide EITHER taskId OR audioId to identify the source track.
253-
Providing audioId ensures the exact track is converted (recommended when a task has multiple tracks).
252+
Per Suno API docs: BOTH taskId AND audioId are REQUIRED parameters.
253+
The audioId specifies which exact track to convert (tasks can have multiple tracks).
254254
255255
Args:
256256
callback_url: Webhook URL for conversion completion notification (required)
257-
task_id: Original generation task ID (taskId) from generate_music (optional)
258-
audio_id: Track ID (audioId) of the specific track to convert (optional)
257+
task_id: Original generation task ID (taskId) from generate_music (required)
258+
audio_id: Track ID (audioId) of the specific track to convert (required)
259259
260260
Returns:
261261
Dictionary containing WAV conversion task information with taskId
262262
263263
Raises:
264264
SunoAPIError: If the API request fails
265-
ValueError: If neither task_id nor audio_id is provided, or if callback_url is missing
265+
ValueError: If required parameters are missing or invalid
266266
267267
Example:
268-
# Option 1: Convert using audio_id (recommended - more precise)
268+
# Generate music and wait for completion
269269
music = await client.generate_music(prompt="Epic soundtrack", wait_audio=True)
270-
track_id = music['data']['sunoData'][0]['id']
271-
conversion = await client.convert_to_wav(
272-
callback_url="https://example.com/webhook",
273-
audio_id=track_id
274-
)
275270
276-
# Option 2: Convert using task_id (converts all tracks from that generation)
277-
generation_task_id = music['data']['taskId']
271+
# Extract BOTH IDs (both required for WAV conversion)
272+
gen_task_id = music['data']['taskId'] # Generation job ID
273+
track_id = music['data']['sunoData'][0]['id'] # Specific track ID
274+
275+
# Convert to WAV (requires both IDs)
278276
conversion = await client.convert_to_wav(
279277
callback_url="https://example.com/webhook",
280-
task_id=generation_task_id
278+
task_id=gen_task_id,
279+
audio_id=track_id
281280
)
282281
"""
283282
if not callback_url:
284283
raise ValueError("callback_url is required for WAV conversion")
285284

286-
if not task_id and not audio_id:
287-
raise ValueError("Either task_id or audio_id must be provided for WAV conversion")
285+
if not task_id:
286+
raise ValueError("task_id is required for WAV conversion (generation job ID)")
287+
288+
if not audio_id:
289+
raise ValueError("audio_id is required for WAV conversion (specific track ID)")
288290

289291
# Validate callback_url format
290292
if callback_url:
@@ -313,27 +315,23 @@ async def convert_to_wav(
313315
)
314316

315317
# Validate task_id format (should be hex string without dashes, or could have dashes)
316-
if task_id:
317-
# Task IDs are typically hex strings (with or without dashes)
318-
# If user accidentally passes a UUID here, warn them
319-
uuid_pattern = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.IGNORECASE)
320-
if uuid_pattern.match(task_id):
321-
raise ValueError(
322-
f"Possible parameter error: task_id '{task_id}' looks like a UUID (track ID). "
323-
f"If this is a track ID, use the audio_id parameter instead: "
324-
f"convert_to_wav(callback_url=..., audio_id='{task_id}')"
325-
)
318+
# Task IDs are typically hex strings (with or without dashes)
319+
# If user accidentally passes a UUID here, warn them
320+
uuid_pattern = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.IGNORECASE)
321+
if uuid_pattern.match(task_id):
322+
raise ValueError(
323+
f"Possible parameter error: task_id '{task_id}' looks like a UUID (track ID). "
324+
f"task_id should be the generation job ID (hex string), not a track UUID. "
325+
f"Check that you're using task_id from music['data']['taskId'], not from sunoData[]['id']"
326+
)
326327

327-
# Build payload - include whichever ID was provided
328+
# Build payload - BOTH IDs are required per API documentation
328329
payload: Dict[str, Any] = {
329-
"callBackUrl": callback_url
330+
"callBackUrl": callback_url,
331+
"taskId": task_id,
332+
"audioId": audio_id
330333
}
331334

332-
if task_id:
333-
payload["taskId"] = task_id
334-
if audio_id:
335-
payload["audioId"] = audio_id
336-
337335
try:
338336
response = await self.client.post("/api/v1/wav/generate", json=payload)
339337
response.raise_for_status()

test_id_validation.py

Lines changed: 0 additions & 136 deletions
This file was deleted.

test_wav_feature.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,21 @@ async def test_validation():
155155
client = SunoClient()
156156

157157
try:
158-
# Test convert_to_wav validation (callback_url required, at least one ID required)
158+
# Test convert_to_wav validation (all three params required)
159159
try:
160-
await client.convert_to_wav("", task_id="test_id")
160+
await client.convert_to_wav("", "test_task_id", "7752c889-3601-4e55-b805-54a28a53de85")
161161
except ValueError as e:
162162
print(f" ✓ convert_to_wav validates callback_url: {str(e)}")
163163

164164
try:
165-
await client.convert_to_wav("https://example.com/webhook")
165+
await client.convert_to_wav("https://example.com/webhook", "", "7752c889-3601-4e55-b805-54a28a53de85")
166166
except ValueError as e:
167-
print(f" ✓ convert_to_wav validates either task_id or audio_id required: {str(e)}")
167+
print(f" ✓ convert_to_wav validates task_id required: {str(e)}")
168+
169+
try:
170+
await client.convert_to_wav("https://example.com/webhook", "test_task_id", "")
171+
except ValueError as e:
172+
print(f" ✓ convert_to_wav validates audio_id required: {str(e)}")
168173

169174
# Test get_wav_conversion_status validation
170175
try:

0 commit comments

Comments
 (0)