omg/docs
SDK

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

functions/api/speak.ts
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!" })
functions/api/transcribe.ts
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 })
image generation
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:

functions/api/generate.ts — returns fast
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/status
functions/api/status.ts
import { 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

ModelKindPrice (billed to your credits)
wavespeed-ai/flux-schnellimage$0.003 / image
wavespeed-ai/flux-devimage$0.012 / image
bytedance/seedream-v4image$0.027 / image
recraft-ai/recraft-20b-svgvector SVG$0.044 / run
wavespeed-ai/wan-2.2/t2v-480p-ultra-fastvideo$0.01 / s (5 s min)
elevenlabs/eleven_turbo_v2_5speech (TTS)$0.05 / 1k chars
elevenlabs/eleven_flash_v2_5speech (TTS)$0.05 / 1k chars
elevenlabs/eleven_multilingual_v2speech (TTS)$0.10 / 1k chars
elevenlabs/scribe_v1transcription (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> tag

Billed 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.