SpeakTrue

Speech Workflow Contract

Reader and action

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.

Scope

The workflow has two HTTP operations:

  1. POST /api/text-to-speech generates a reusable speech artifact.
  2. 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.

The Flask web routes described below require the signed-in user’s Supabase bearer token. This September 4 web ownership change does not alter native Supabase Edge Function contracts. Web playback and download URLs must be fetched with the bearer token, then rendered/downloaded as browser Blob URLs; never put the token in the URL. Only approved same-origin media endpoints receive that token.

SUPABASE_EDGE_STRICT=true remains the default, but category and clip management always require a verified bearer identity and the per-user Supabase database index, including when strict mode is off. New objects use users/<verified-user>/soundboard/<category-uuid>/; reads follow validated stored paths so category/clip moves do not relocate existing media. Both adapters use the same ownership contract; application startup remains Supabase-only. There is no fallback to global categories or unindexed shared objects. Existing shared data requires an explicit ownership mapping before any separate data migration; see docs/ops/WEB_IOS_SECURITY_REMEDIATION.md for verification and rollout limits.

Generate speech request

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:

Optional fields fall back to server defaults when omitted. Numeric tuning fields must be parseable as numbers.

Generate speech success

HTTP status: 200

{
  "success": true,
  "status": 200,
  "artifact_id": "generated-id",
  "artifact_path": "speech/user-id/generated-id.mp3",
  "audio_path": "/api/generated-media?path=speech/user-id/generated-id.mp3",
  "download_path": "/api/generated-media?download=1&path=speech/user-id/generated-id.mp3"
}

Client rules:

Save to Soundboard request

Generation snapshots and local-model fields follow Saved clip metadata.

POST /api/soundboard/save-clip

{
  "category": "Lecture1",
  "text": "Text used to generate the speech",
  "artifact_path": "speech/user-id/generated-id.mp3",
  "artifact_id": "generated-id",
  "audio_path": "/api/generated-media?path=speech/user-id/generated-id.mp3",
  "download_path": "/api/generated-media?download=1&path=speech/user-id/generated-id.mp3",
  "format": "mp3",
  "bitrate_kbps": 192,
  "normalize": true
}

Required:

Compatibility:

Save to Soundboard success

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:

Failure envelope

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:

Details rules:

Status and retryability expectations

Artifact path rules

Browser diagnostics

The legacy web runtime exposes:

Diagnostics include generation/save phase, operation, error code, status, retryability, message, and bounded details where available.

Native consumer checklist

Executable proof

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: