Media & speech
Zero-config image, video, speech (TTS) and transcription (STT) generation from any Vibes app. No keys, jobs billed to your credits, results on a durable CDN.
Every Vibes app can generate media through the platform with no setup: import
from @omg-dev/media, pick a curated model, pass params. The runtime injects
the endpoint, holds the provider keys host-side, bills your credits
(pass-through provider price, no margin), and uploads results to a durable
public CDN.
TL;DR
import { generateSpeech } from "@omg-dev/media"
// Text → spoken audio. Returns a durable CDN mp3 URL in a few seconds.
const { url } = await generateSpeech({ text: "Welcome back!" })import { transcribe } from "@omg-dev/media"
// Speech → text. audioUrl must be a public URL (e.g. an uploaded file).
// durationSeconds is required — it's what the transcription is billed on.
const { text } = await transcribe({ audioUrl, durationSeconds: 42 })import { generateImage } from "@omg-dev/media"
const { url } = await generateImage({
model: "wavespeed-ai/flux-dev",
input: { prompt: "a corgi astronaut, studio lighting" },
})The one rule: don't block a route on a slow job
Requests through the platform edge time out after ~100 s. The blocking
wrappers (generateImage, generateVideo, generateSpeech, transcribe)
poll to completion inside your call — fine for speech (seconds) and
borderline for fast image models, but video (1–5 min) will time out every
time. For anything slow, use the raw job API and return immediately:
import { submit } from "@omg-dev/media"
const job = await submit({ model: "wavespeed-ai/flux-dev", input: { prompt } })
return { jobId: job.jobId } // 202-style; client polls /api/statusimport { getJob } from "@omg-dev/media"
return await getJob(jobId) // { status, results: [{ url, text? }], error }Client side, poll with the bundled hook:
import { useMediaJob } from "@omg-dev/media/react"
const { status, results } = useMediaJob(`/api/status?id=${jobId}`)Even better for apps: store a row per generation, complete it from an
on() event handler (which runs in the background, off the request path),
and render reactively with useCollection — the result appears with no
client polling at all.
Models
| Model | Kind | Price (billed to your credits) |
|---|---|---|
wavespeed-ai/flux-schnell | image | $0.003 / image |
wavespeed-ai/flux-dev | image | $0.012 / image |
bytedance/seedream-v4 | image | $0.027 / image |
recraft-ai/recraft-20b-svg | vector SVG | $0.044 / run |
wavespeed-ai/wan-2.2/t2v-480p-ultra-fast | video | $0.01 / s (5 s min) |
elevenlabs/eleven_turbo_v2_5 | speech (TTS) | $0.05 / 1k chars |
elevenlabs/eleven_flash_v2_5 | speech (TTS) | $0.05 / 1k chars |
elevenlabs/eleven_multilingual_v2 | speech (TTS) | $0.10 / 1k chars |
elevenlabs/scribe_v1 | transcription (STT) | $0.22 / audio-hour (60 s min) |
Unknown model ids are rejected at submit — the catalog is curated.
Speech (text → audio)
const { url } = await generateSpeech({
text: "Your order has shipped!",
voiceId: "21m00Tcm4TlvDq8ikWAM", // optional; platform default voice otherwise
model: "elevenlabs/eleven_multilingual_v2", // optional; turbo_v2_5 default
})
// url → https://…/gen/<jobId>/0.mp3 — durable, play it in an <audio> tagBilled per character of text. Returns in a few seconds, so calling it
directly in a route is acceptable.
Transcription (audio → text)
const { text } = await transcribe({
audioUrl: publicUrl, // must be fetchable — e.g. a file uploaded to public/uploads/
durationSeconds: 42, // REQUIRED: the billing basis ($0.22/hour, 60s minimum)
})To transcribe a user recording: upload the blob first (so it has a public
URL), read the duration off the recorder/file metadata, then call
transcribe. The transcript also appears on the raw job as
results[0].text.
End-user attribution
Wrap handlers with runWithEndUser(userId, fn) (same pattern as
@omg-dev/ai) and every generation inside is stamped with that end user for
your per-user dashboards. Costs are billed to you, the app creator, not
passed to end users.