This contract is for native and web engineers implementing the generated speech to Soundboard save workflow. After reading it, a caller should be able to generate speech, retain the returned artifact reference, save that artifact to Soundboard, and branch correctly on structured failures without inspecting legacy planning artifacts.
The workflow has two HTTP operations:
POST /api/text-to-speech generates a reusable speech artifact.POST /api/soundboard/save-clip saves that generated artifact into a Soundboard category.Privileged storage writes remain server-side. Browser and native clients only forward artifact metadata returned by the generation operation; they do not write directly to object storage.
POST /api/text-to-speech
{
"text": "Text to speak",
"voice": "voice-id",
"model": "eleven_multilingual_v2",
"stability": 0.8,
"similarity": 0.7,
"speed": 0.9,
"style": 0.2,
"speaker_boost": true
}
Required:
text: non-empty string.Optional fields fall back to server defaults when omitted. Numeric tuning fields must be parseable as numbers.
HTTP status: 200
{
"success": true,
"status": 200,
"artifact_id": "generated-id",
"artifact_path": "speech/anonymous/generated-id.mp3",
"audio_path": "/static/speech/anonymous/generated-id.mp3",
"download_path": "/download-audio?path=speech/anonymous/generated-id.mp3"
}
Client rules:
success: true, numeric status: 200, artifact_id, artifact_path, audio_path, and download_path as required.artifact_path when saving; keep audio_path for playback and compatibility.POST /api/soundboard/save-clip
{
"category": "Lecture1",
"text": "Text used to generate the speech",
"artifact_path": "speech/anonymous/generated-id.mp3",
"artifact_id": "generated-id",
"audio_path": "/static/speech/anonymous/generated-id.mp3",
"download_path": "/download-audio?path=speech/anonymous/generated-id.mp3",
"format": "mp3",
"bitrate_kbps": 192,
"normalize": true
}
Required:
category: target Soundboard category path.text: original generated text.artifact_path: canonical generated artifact path from the generation response.Compatibility:
audio_path is accepted by the server, but new callers should send artifact_path.artifact_id and download_path are forwarded for diagnostics and continuity; they are not privileged storage credentials.HTTP status: 200
{
"success": true,
"message": "Clip and text saved to Lecture1 category in storage",
"clip": {
"name": "Text_to_speak",
"filename": "Text_to_speak_1712345678.mp3",
"url": "https://storage.example/soundboard/Lecture1/Text_to_speak_1712345678.mp3",
"text_url": "https://storage.example/soundboard/Lecture1/Text_to_speak_1712345678.txt",
"text_filename": "Text_to_speak_1712345678.txt",
"category": "Lecture1",
"timestamp": 1712345678
}
}
Client rules:
success: true, clip.filename, and clip.category as required.Generation and save failures use the same structured envelope:
{
"error": "Human-readable failure message",
"error_code": "SPEECH_ARTIFACT_NOT_FOUND",
"operation": "save-clip-to-soundboard",
"status": 404,
"retryable": false,
"details": {
"phase": "audio_upload",
"backend": "supabase"
}
}
Required fields:
error: human-readable message.error_code: stable machine-readable code.operation: tts-generate or save-clip-to-soundboard.status: numeric status for Flask speech workflow errors; Supabase Edge provider errors may include provider_status separately.retryable: boolean retry guidance.Details rules:
details is optional and bounded.400, retryable: false.401/403, retryable: false.404, retryable: false.500 or 502, usually retryable: true.artifact_path is a server-relative generated speech path such as speech/anonymous/<id>.mp3.audio_path is the browser-playable /static/... form of that artifact.latestTTSSpeechArtifact and block save.The legacy web runtime exposes:
window.latestTTSSpeechArtifact: the current reusable generated artifact or null.window.__ttsSoundboardWorkflowTestHooks.getState(): latest artifact, generation diagnostic, save diagnostic, and saving flag.Diagnostics include generation/save phase, operation, error code, status, retryability, message, and bounded details where available.
artifact_path, artifact_id, audio_path, and download_path together.artifact_path and original text when saving to Soundboard.error_code, operation, status, and retryable; do not parse prose messages.Run these checks before changing the contract:
node web/python-web-app/tests/js/tts_soundboard_workflow_runtime_check.mjs
web/python-web-app/venv/bin/pytest web/python-web-app/tests/api/test_speech_contract_hardening.py web/python-web-app/tests/api/test_tts_soundboard_workflow_target.py web/python-web-app/tests/api/test_tts.py web/python-web-app/tests/api/test_soundboard_supabase_mode.py
deno test backend/supabase/functions/tts-generate/handler_test.ts
deno test backend/supabase/functions/soundboard-save-generated/handler_test.ts
The graph rebuild command keeps code navigation artifacts current after implementation changes:
python3 -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"