Video and Voice
Asynchronous video on Queues, speech synthesis, prices, limits, the task APIs and provider keys.
Before you start
Voice defaults to OpenAI and requires OPENAI_API_KEY. Video keeps its separate provider adapter and requires DASHSCOPE_API_KEY; the existing alternative voice model uses the same key. Missing keys hide the corresponding media models in live mode. Mock mode serves playable sample media without keys. See AI overview.
Video
Video has no OpenAI default: the OpenAI Videos API shut down on September 24, 2026. Keep the bundled video provider configured separately.
| Setting | Value |
|---|---|
| Length | 2 to 10 seconds |
| Resolution and price | 480P: 6, 720P: 12, 1080P: 23 credits per second |
| Ratios | 16:9, 9:16, 1:1, 4:3, 3:4 |
| Input | Text, or text plus a first frame from the library (image-to-video renders 1:1) |
| Audio | Generated with the clip unless audio is false |
A 5-second 720P clip reserves 60 credits.
How a video task runs
/app/videosendsPOST /api/ai/videos. The server prices resolution × seconds and reserves the credits with the task and its outbox message.- The queue consumer leases the task and the adapter submits the provider job. Its id is stored on the task.
- The lease is released and a delayed message (
JOBS.send(..., { delaySeconds })) checks again later. Nothing waits inside a request, and people can close the tab. - When the job finishes, the MP4 is copied into R2 and served from
/api/ai/assets/{id}/contentwith range requests, so the player can seek. - A job still unfinished 30 minutes after its first claim fails and returns its credits. The five-minute cron also reclaims expired leases.
{
"requestId": "0b8d5e2a-7f3c-4c1d-9a6e-2f4b8c1d3e5a",
"model": "your-video-model-key",
"prompt": "Steam rises from the coffee, the camera slowly pushes in",
"resolution": "720P",
"ratio": "16:9",
"seconds": 5,
"audio": true,
"firstFrame": "optional-library-asset-id"
}The prompt is up to 2,000 characters. The model key comes from VIDEO_MODELS in packages/ai/src/video.ts.
Voice
The default model is gpt-4o-mini-tts. openaiSpeech sends requests to /v1/audio/speech and stores WAV output. Pricing is max(1, ceil(characters / 1000 × 5)), so a 1,000-character script costs 5 credits.
| Setting | Value |
|---|---|
| Voices | coral (default), alloy, marin, cedar, nova, sage; multilingual voices |
| Script | Up to 2,000 characters per clip |
| Price | 5 credits per 1,000 characters, rounded up, so every clip costs at least 1 credit |
| Output | WAV in the library, playable and downloadable |
POST /api/ai/speech takes requestId, model, voice and text. The task is reserved, leased from JOBS, synthesized by the adapter and written to R2 like every other output. Voice keys and labels live in SPEECH_MODELS in packages/ai/src/speech.ts.
Shared behavior
- Both endpoints return
201with the task and refuse with402,429or503like the image studio. GET /api/ai/tasks/{id}reports progress;POST /api/ai/tasks/{id}/cancelcancels a queued task and returns its credits.- Provider content refusals return credits while
ai.refundOnContentRefusalistrue. - Each task's estimated upstream cost counts toward
ai.dailyBudgetUsd.
Troubleshooting
- Model missing in live mode: the provider key is not set for this environment.
- Stuck in rendering: check that the queue consumer is deployed, the
JOBSbinding exists and the cron trigger runs. The task fails with a refund after 30 minutes. - Video does not seek: load it through
/api/ai/assets/{id}/content, which answers range requests, not through a copied URL.
Speech API lifecycle
The OpenAI deprecation schedule lists January 6, 2027 as the Speech API shutdown date. Migrate the adapter to the Realtime API before then and revalidate audio format, usage accounting and playback. Replacing the model name in /audio/speech alone is not a migration.