Media generation
POST /api/media/generate — asynchronous generation of images, video, TTS, music and 3D. Parameters model, prompt, opts. Poll with GET /api/media/jobs/{id}.
Async media generation: images, video, voiceover (TTS), speech-to-text, music and 3D. You create a task and poll the result. The full model list is in the media-models catalog; webhooks, errors and billing are in the webhook section.
Create parameters
| Parameter | Type | Description |
|---|---|---|
modelreq. | string | A media model name, e.g. veo-3.1 or gpt-image-2 (see the catalog). |
promptreq. | string | Text description. |
opts | object | Additional generation options (model-dependent): resolution, duration and so on. |
Create and poll
Creating returns 202 with { ok: true, jobId, status: "processing" }. Poll GET /api/media/jobs/{jobId} until status: "success", then take the resultUrls.
# 1. create a generation job
curl https://getvibeapi.com/api/media/generate \
-H "Authorization: Bearer $GETVIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1",
"prompt": "a cat on a skateboard, slow motion"
}'
# → {"ok":true,"jobId":"...","status":"processing"}ok: false and no charge. Site prices are indicative; the exact charge is returned in the x-pv-cost-rub header of each accepted response. Stream bodies are unchanged by default; send x-pv-cost: 1 to add the cost to that protocol's final usage event.Advanced parameters (opts)
Besides model and prompt, you can pass extra options in opts. The set depends on the model. The full machine-readable contract is via GET /api/model-capabilities (or GET /api/model-capabilities/{model} for a specific model).
| Parameter | Type | Description |
|---|---|---|
aspect_ratio | string | Aspect ratio: 1:1, 16:9, 9:16, 4:3, 3:4. |
duration | string|number | Video duration in seconds (5, 10, 15). |
resolution | string | Resolution: 480p, 720p, 1080p, 4K. |
image_url | string (URL) | Source image for I2V / image-to-image. |
negative_prompt | string | What to exclude from generation (qwen-image, wan-2-7). |
guidance | number | Guidance scale — prompt adherence (flux-2, qwen-image, seedance). |
seed | number | Seed for a reproducible result (flux-2). |
stability | number | Stability 0–1 (hailuo, happyhorse, seedance, TTS). |
camera | string | Camera motion, e.g. "pan left" (kling-3.0, hailuo). |
first_frame_url | string (URL) | First frame for I2V (kling-3.0, wan-2-7). |
last_frame_url | string (URL) | Last frame for I2V (seedance, kling-3.0, wan-2-7). |
reference_image | string (URL) | Style/character reference (happyhorse). |
similarity_boost | number | Voice similarity 0–1 (ElevenLabs TTS). |
speed | number | Speech rate 0.5–2 (ElevenLabs TTS). |
GET /api/model-capabilities/{model} response.Your own files: base64 instead of a link
URL options (image_url, input_urls, mask_url and the rest) are links the provider fetches, so they accept only public https:// addresses. An inline data:image/...;base64,… string in one of those fields is rejected with 400.
To send your own file, use the matching *_base64 field instead: the service stores the bytes, publishes them and passes the resulting link to the provider. Up to 16 files per request in input_base64.
| Parameter | Type | Description |
|---|---|---|
image_base64 | string | Source image. Replaces image_url. |
mask_base64 | string | Inpainting mask (image/* only). Replaces mask_url. |
input_base64 | string[] | References for image-to-image and multi-input. Replaces input_urls. |
audio_base64 | string | Source audio. Replaces audio_url. |
video_base64 | string | Source video. Replaces video_url. |
data:image/png;base64,… string including the prefix — the file type is read from it.Request contract: strict, quote_id, max_price_rub
POST /api/media/generate and POST /api/media/price accept the same body, so you can quote it and then submit it unchanged. There are no other top-level fields: an unknown one is refused on both endpoints with 400 and code unknown_field naming it (field, fields), with or without strict. Generation parameters go inside opts.
| Parameter | Type | Description |
|---|---|---|
modelreq. | string | Public model id or one of its routes (base:alias). |
prompt | string | Text; required only when the model operation requires it. |
opts | object | Model options. Exact schema: operations[].requestJsonSchema in GET /v1/model-capabilities/{model}. |
strict | boolean | Strict mode (or the header x-getvibe-strict: 1): a request that would not run exactly as written (a parameter dropped, a value clamped, a key outside the model contract) is refused with 422 strict_validation_failed and a violations list, before any quote or charge. A documented field the model accepts and ignores (duration on source-length models) is not a violation: it comes back as a param_ignored warning. |
quote_id | string | mq_… from POST /api/media/price. The job runs only at that price; a stale, used or re-routed quote is refused with 409/422 before anything runs. |
max_price_rub | number | Ceiling for this one job in the account currency. If the quote is higher: 422 media_max_price_exceeded and nothing runs. A metered job is never charged above it. |
webhook_url | string | Public https URL that receives a signed completion notification. |
Files by id (Files API)
Any source file can be sent three ways: as a link (image_url, audio_url, video_url…), as inline bytes (image_base64, audio_base64, video_base64…) or as the id of your own file from POST /v1/files. The id fields are listed in every operation’s requestJsonSchema and are accepted in strict mode.
| Parameter | Type | Description |
|---|---|---|
image_file_id | string | Instead of image_url. |
audio_file_id | string | Instead of audio_url. |
video_file_id | string | Instead of video_url. |
mask_file_id | string | Instead of mask_url. |
first_frame_file_id / last_frame_file_id | string | Instead of first_frame_url / last_frame_url. |
input_file_ids | string[] | Appended to input_urls. |
reference_*_file_ids | string[] | Appended to reference_image_urls / reference_audio_urls / reference_video_urls. |
Video priced by the input file’s length
For models whose output length follows an uploaded track — kling-ai-avatar, omnihuman-v2, omnihuman-2-0, omnihuman-v1.5, omnihuman-1-5, volcengine-lipsync (audio), kling-motion-control, wan-animate, topaz-video-upscale, p-video-edit (video), lipsync-2-pro, sync-3, react-1 (both inputs) — /api/media/price and the max_price_rub pre-flight on /api/media/generate measure that file server-side (link, data: URI, base64 or *_file_id) and price the measured seconds. The answer carries sourceSeconds (outputSeconds for lip-sync), and estimateReason says the length was measured. If the file cannot be read, the price keeps its previous basis (one second, the catalog estimate or the route maximum) and estimateReason says so. The charge is the per-second rate × the seconds actually rendered, never above max_price_rub. opts.duration on these models is accepted and ignored with a param_ignored warning.
Voice cloning
Create: { name, consent: true, engine, audio_url | audio_base64 } or several recordings samples: [{ audio_url } | { audio_base64 }, …] (engine: "elevenlabs" only). An ElevenLabs voice is used by voice-clone-tts and by the Voice Changer (elevenlabs-voice-changer). Appending recordings to an ElevenLabs voice is free. Every recording is downloaded and measured before the supplier is called: a refusal changes and charges nothing.
| Parameter | Type | Description |
|---|---|---|
one recording | ≤ 10 MB | wav/mp3, up to 60 s. Larger: 422 voice_sample_too_large / voice_sample_invalid. |
recordings per voice | ≤ 25 | Counting those already added. More: 422 voice_samples_limit. |
voice length | ≤ 300 s | All recordings together, counting those already added. Longer: 422 voice_samples_limit; shorter than the engine minimum: 422 voice_samples_invalid. |
recordings size | ≤ 50 MB | All recordings of one request together. More: 422 voice_samples_too_large. |
request body | ≤ 70 953 644 B | 50 MB of recordings as base64 (×4/3) plus JSON. Larger: 413 payload_too_large (JSON, with max_body_bytes). Send long sets in parts via …/samples, or as audio_url links. |
additions per 24 h | 20 | Per client. More: 429 voice_samples_rate_limited. |
Voice Changer and Forced Alignment
| Parameter | Type | Description |
|---|---|---|
elevenlabs-voice-changer | model | opts.voice_id (your ElevenLabs voice) + the source recording opts.audio_url | audio_base64 | audio_file_id, up to 5 minutes. Priced on the measured length: 1 minute = 1000 characters. |
forced-alignment | model | prompt is the transcript, opts.audio_url | audio_base64 | audio_file_id the recording. Result: word timings in resultMeta.words and an SRT file in resultUrls. Priced per 1000 characters of text. |
Sandbox: unknown submit outcome
When the supplier’s start confirmation does not arrive, the job stays in status: "submitting" with stage: "submission_unknown", errorCode: "media_submission_uncertain" and failure.advice: "reconcile_first" — keep polling the same jobId and do not create a duplicate. To test this path at no cost, send POST /api/media/generate with a sandbox key and the header x-pv-sandbox-outcome: submission_uncertain. The answer is a normal 202 with a jobId; polling it answers as an unknown submit for 60 seconds, then as a job that never started (failure.code: "submission_not_started", charged: false). Nothing is stored, sent to a supplier or charged. A regular key with this header gets 400 sandbox_outcome_requires_sandbox_key.
Speech-to-text (transcription)
Same endpoint as the rest of generation: put the STT model name in model, leave prompt as an empty string, and pass the recording in opts.audio_url. The transcript arrives in resultText on GET /api/media/jobs/{id}; resultUrls stays empty — transcription produces no file.
# speech-to-text: create the job, then read the transcript
curl https://getvibeapi.com/api/media/generate \
-H "Authorization: Bearer $GETVIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "whisper-large-v3-turbo",
"prompt": "",
"opts": {
"audio_url": "https://example.com/meeting.mp3",
"language_code": "en"
}
}'
# → {"ok":true,"jobId":"JOB_ID","status":"processing","priceRub":0.03}
curl https://getvibeapi.com/api/media/jobs/JOB_ID \
-H "Authorization: Bearer $GETVIBE_API_KEY"
# → {"status":"success","resultText":"the transcribed text…","resultUrls":[],"priceRub":0.03}Models
whisper-large-v3-turbo— $0.00025 per minute of audio. Cheapest and fastest, 99 languages.nemotron-asr-multilingual— $0.00025 per minute. NVIDIA multilingual ASR, punctuates on its own.whisper-large-v3— $0.000563 per minute. Full Whisper: slower than turbo, more accurate on noisy audio.universal-2— $0.003125 per minute.speech-to-text— $0.01 per minute (ElevenLabs Scribe). Supports speaker diarization and audio-event tags.
Parameters
| Parameter | Type | Description |
|---|---|---|
audio_urlreq. | string (URL) | Public https link that serves the file directly. Redirects and private network addresses are refused. Use audio_base64 instead if the file is not online. |
audio_base64 | string | The recording inline as data:audio/mpeg;base64,… Replaces audio_url. |
language_code | string | Language code (en, ru, …). Detected automatically when omitted. |
duration_minutes | number | Length of the recording in minutes — used for the up-front price estimate. The final charge is measured (see below). |
diarize | boolean | Speaker diarization (universal-2, speech-to-text). |
tag_audio_events | boolean | Tag audio events such as laughter or applause (speech-to-text). |
keyterms | string[] | Hint list of terms and names (speech-to-text; +30% price). |
Formats and limits
mp3, wav, m4a/mp4, flac, ogg/opus and webm are accepted. File size is 25 MB maximum — roughly 26 minutes of 128 kbps mp3, or several hours of speech in opus. Anything larger is refused immediately with an explicit message and is not billed; split the recording instead.
whisper-large-v3-turbo, whisper-large-v3 and nemotron-asr-multilingual the final amount is computed from the measured length of the recording, taken from the transcript — not from whatever you passed in duration_minutes. The minimum charge is one kopeck. The exact amount is always returned as priceRub on the finished job.Readiness webhook (instead of polling)
To avoid polling, register one address mediaWebhookUrl in your dashboard. When the task completes (success or error), the service makes up to five delivery attempts of a POST with a JSON body:
// POST to your mediaWebhookUrl when a job finishes
{
"jobId": "...",
"status": "success", // or "fail"
"model": "veo-3.1",
"kind": "video", // image | video | audio | music | 3d
"resultUrls": ["https://getvibeapi.com/api/media/file/..."],
"failMsg": null,
"createdAt": "2026-06-17T10:00:00.000Z"
}Fetching the file: /api/media/file/{id}/{idx}
The generation result URL (image, video, audio or 3D model) from resultUrls. Use it as returned by the service: it opens without an API key and works in <img>, <video> or a browser. The provider is hidden: the upstream address is resolved server-side, the client never sees it.
| Parameter | Type | Description |
|---|---|---|
idreq. | string (path) | Task id (jobId) from the /api/media/jobs/{id} response. |
idxreq. | integer (path) | Result index (0-based). For a single result — 0. |
query params | as in resultUrls | Do not build the URL yourself or modify the parameters returned by the service. |
download | string (query) | download=1 returns the file as an attachment whose name and extension follow the content (.glb for a 3D model). Without it the file opens in the browser. |
# The signed URL is returned in resultUrls of the /api/media/jobs/{id} response
# or in the webhook body. Opens directly in the browser / <img> — no key needed.
https://getvibeapi.com/api/media/file/JOB_ID/0?sig=HMAC_SIGNATUREresultsExpireAt field of /api/media/jobs/{id}). The finished file is copied to our storage and resultUrls switch to a permanent address of the form https://getvibeapi.com/media/r/…; a previously returned /api/media/file/… URL keeps working too. Both support Range requests (seeking in video and audio). After 7 days the file is deleted and the URL answers 410 Gone with the code media_result_expired — download what you need before then.