API > Integrate API into your product
Video Generation API v2 — Text, Image & Reference to Video
Base URL: https://api.phantaisia.com
All requests require a Bearer token in the Authorization header.
Authorization: Bearer <your_token>
Looking for the previous video endpoints?
single_frame_videoandfirst_last_frame_videoare still available and documented in How to Use the Video API Endpoint. The endpoints on this page are the new generation: per-second pricing, free duration from 5 to 15 seconds, selectable resolution, and three generation modes.
Overview
There are three ways to generate a video:
| Endpoint | What it does | Input you provide |
|---|---|---|
POST /v1/run/text_to_video |
Generates a video from a text prompt alone | Prompt (or a Video Filter) |
POST /v1/run/image_to_video |
Animates a start image, optionally towards an end image | Start frame + optional end frame — or a selfie + Video Filter |
POST /v1/run/reference_to_video |
Transfers the motion of a reference video onto your subject, guided by up to three reference images | Guest photo + optional reference images, reference video and audio |
Every run endpoint returns a job ID immediately. Each mode has its own status endpoint — see Check Job Status.
Two ways to call each endpoint
Every endpoint can be called directly or with a Video Filter.
| Mode | How | When to use it |
|---|---|---|
| Direct | You send the prompt and all render settings in the request | Full control, you manage prompts and settings in your own system |
| Video Filter | You send a video_filter_id (UUID of a Video AI Filter created in the Filter Creator) and the gateway pulls the prompt, render settings and face swap filters from it |
Recommended. Set up the look once in the Filter Creator, then only send the guest's photo and the duration |
Video Filters are created in the Filter Creator — see Faceswap: Video Filter. Each Video Filter has a generation mode that determines which endpoint it works with:
| Video Filter generation mode | Endpoint to call | What the filter supplies |
|---|---|---|
text_to_video |
/v1/run/text_to_video |
Prompt, resolution, aspect ratio |
single_frame |
/v1/run/image_to_video |
Prompt, resolution, aspect ratio, one face swap filter (start frame) |
first_last_frame |
/v1/run/image_to_video |
Prompt, resolution, aspect ratio, two face swap filters (start + end frame) |
reference_to_video |
/v1/run/reference_to_video |
Prompt, resolution, aspect ratio, reference images, reference video, audio |
Override rules when a filter is used:
resolution_mpandaspect_ratiosent in the request override the filter's values.promptin the request overrides the filter's prompt ontext_to_videoonly. Onimage_to_videoandreference_to_videothe filter's prompt is always used.- On
reference_to_video,video,audioandinclude_video_audioin the request override the filter's own. The filter's reference images win overreference_imagesin the request when the filter has any.
Shared parameters
These fields behave the same on all three endpoints.
| Field | Type | Description |
|---|---|---|
duration |
integer |
Video length in seconds, 5 to 15. Always required. Note: an integer, not "8s" as on the previous endpoints |
resolution_mp |
number |
Output size in megapixels: 0.7 (Draft), 0.9 (HD), 1.5 (HD+), 2.0 (Full HD). Required for direct calls on text_to_video and image_to_video; otherwise falls back to the filter's quality, then 1.5 |
aspect_ratio |
string |
"1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9". Required for direct calls on text_to_video and image_to_video; otherwise falls back to the filter's ratio, then "16:9" |
video_filter_id |
string |
UUID of a Video AI Filter. Optional — see Two ways to call each endpoint |
email |
string |
Optional. When set, the finished video link is emailed to this address once, as soon as the job completes. Useful for guests who leave the booth before the video is ready |
Images (first_frame, last_frame, img, prompt_image, reference_images) accept either a base64 data URI (data:image/jpeg;base64,...) or a publicly accessible URL. Base64 images are uploaded to our storage before generation. Reference videos and audio (video, audio) must be public URLs.
Pricing
The new endpoints are billed per second of generated video, so the cost scales linearly with duration. Credits are deducted when the job is accepted; if you do not have enough credits the request fails with 422 before any job is created.
| Endpoint | Pricing category | Quick rate | Advanced rate | Face swaps |
|---|---|---|---|---|
text_to_video |
Text2filter | 1 credit / second | 1.5 credits / second | none |
image_to_video |
Text2filter | 1 credit / second | 1.5 credits / second | + 1 credit per face swap run by the filter (1 for a single_frame filter, 2 for a first_last_frame filter) |
reference_to_video |
Video2video | 2 credits / second | 2.5 credits / second | none |
Which rate applies? The rate is chosen by the Video Filter's video_model:
advanced→ Advanced ratesimple,performanceor any other value → Quick rate- Direct calls without a filter → always Quick rate
Examples
| Request | Calculation | Cost |
|---|---|---|
text_to_video, direct, 10 s |
10 × 1 | 10 credits |
image_to_video, single_frame filter on simple, 8 s |
1 face swap + 8 × 1 | 9 credits |
image_to_video, first_last_frame filter on advanced, 8 s |
2 face swaps + 8 × 1.5 | 14 credits |
reference_to_video, direct, 8 s |
8 × 2 | 16 credits |
reference_to_video, filter on advanced, 15 s |
15 × 2.5 | 37.5 credits |
For general credit information see What are the API prices and how much are my credits worth.
1. Text to Video
POST /v1/run/text_to_video
Generates a video from a text prompt. No input image is needed, so no face swap is run — this mode is for scenery, intros, idents and other non-personalized clips.
Request body — direct
{
"prompt": "A hot air balloon drifts over a misty valley at sunrise.",
"duration": 8,
"resolution_mp": 1.5,
"aspect_ratio": "16:9"
}
Request body — with a Video Filter
{
"video_filter_id": "uuid-of-video-ai-filter",
"duration": 8
}
| Field | Type | Required | Description |
|---|---|---|---|
prompt |
string |
Direct: Yes | Text description of the scene and motion. With a filter it is optional and overrides the filter's prompt |
duration |
integer |
Yes | 5–15 seconds |
resolution_mp |
number |
Direct: Yes | 0.7, 0.9, 1.5 or 2.0 |
aspect_ratio |
string |
Direct: Yes | See Shared parameters |
video_filter_id |
string |
No | UUID of a text_to_video Video Filter |
email |
string |
No | Finished video link is emailed here once |
Response — 200 OK
{
"id": "t2v-3f9a1c2b7d4e",
"status": "IN_QUEUE",
"output": {}
}
Poll POST /v1/status/text_to_video/{job_id} with the id.
Errors
| HTTP | error_code |
Meaning |
|---|---|---|
404 |
VIDEO_FILTER_NOT_FOUND |
No Video Filter with that video_filter_id |
422 |
INSUFFICIENT_CREDITS |
Not enough credits for this request |
422 |
— | Validation error: duration outside 5–15, invalid resolution_mp / aspect_ratio, or prompt, resolution_mp, aspect_ratio missing on a direct call |
2. Image to Video
POST /v1/run/image_to_video
Animates a start image into a video. Add an end image and the model interpolates the motion between the two frames, guided by your prompt. This is the successor of both single_frame_video and first_last_frame_video.
Request body — direct
{
"prompt": "The subject turns towards the camera and smiles.",
"duration": 8,
"resolution_mp": 1.5,
"aspect_ratio": "9:16",
"first_frame": "<base64-or-url>",
"last_frame": "<base64-or-url>"
}
Leave out last_frame to animate from the first frame alone.
Request body — with a Video Filter
{
"video_filter_id": "uuid-of-video-ai-filter",
"duration": 8,
"img": "<base64-or-url-of-selfie>",
"gender": 1
}
With a filter the gateway runs the face swap for you: the guest's selfie (img) is swapped through the filter's start-frame AI filter and, if the filter has one, its end-frame AI filter. The swapped images become the first and last frame — you do not send first_frame / last_frame yourself.
| Field | Type | Required | Description |
|---|---|---|---|
prompt |
string |
Direct: Yes | Text description of the motion. Ignored when a filter is used (the filter's prompt applies) |
duration |
integer |
Yes | 5–15 seconds |
resolution_mp |
number |
Direct: Yes | 0.7, 0.9, 1.5 or 2.0 |
aspect_ratio |
string |
Direct: Yes | See Shared parameters |
first_frame |
string |
Direct: Yes | Start image, base64 or public URL |
last_frame |
string |
No | End image, base64 or public URL. Omit to animate from the start frame only |
video_filter_id |
string |
No | UUID of a single_frame or first_last_frame Video Filter |
img |
string |
Filter: Yes | The guest's selfie, base64 or public URL — the face that gets swapped in |
gender |
integer |
No (default 0) |
0 = Male, 1 = Female, 2 = Boy, 3 = Girl. Used for the face swaps |
email |
string |
No | Finished video link is emailed here once |
Response — 200 OK
{
"id": "i2v-8c21e4a90b7f",
"status": "IN_QUEUE",
"output": {}
}
Poll POST /v1/status/image_to_video/{job_id} with the id.
Errors
| HTTP | error_code |
Meaning |
|---|---|---|
400 |
VIDEO_FILTER_MISSING_FILTER |
The Video Filter has no AI filter attached, so there is nothing to face swap (for example a text_to_video filter) |
404 |
VIDEO_FILTER_NOT_FOUND |
No Video Filter with that video_filter_id |
422 |
INSUFFICIENT_CREDITS |
Not enough credits for face swap(s) + video |
422 |
— | Validation error: duration outside 5–15, missing img with a filter, or prompt, resolution_mp, aspect_ratio, first_frame missing on a direct call |
If a face swap fails, the video job ends in failed — see Check Job Status.
3. Reference to Video
POST /v1/run/reference_to_video
Generates a video where a reference video drives the motion and up to three reference images carry the subject. The guest's photo (prompt_image) is always the first reference image; up to two more can follow. No face swap is run in this mode — the model works from the reference images directly.
Request body — direct
{
"prompt": "The subject walks through a neon-lit street.",
"duration": 8,
"resolution_mp": 1.5,
"aspect_ratio": "9:16",
"prompt_image": "<base64-or-url-of-guest-photo>",
"reference_images": ["<base64-or-url-of-ref-1>", "<base64-or-url-of-ref-2>"],
"video": "https://example.com/reference.mp4",
"include_video_audio": true
}
Request body — with a Video Filter
{
"video_filter_id": "uuid-of-video-ai-filter",
"duration": 8,
"prompt_image": "<base64-or-url-of-guest-photo>"
}
| Field | Type | Required | Description |
|---|---|---|---|
prompt_image |
string |
Yes | The guest's photo, base64 or public URL. Always the first reference image |
duration |
integer |
Yes | 5–15 seconds |
prompt |
string |
Direct: Yes | Text description of the scene and motion. Ignored when a filter is used (the filter's prompt applies) |
reference_images |
string[] |
No | Up to 2 further reference images, in order after the guest photo. Ignored when the filter has its own reference images |
video |
string |
No | Public URL of the reference video that drives the motion. Overrides the filter's reference video |
include_video_audio |
boolean |
No | Keep the reference video's audio in the output. Only has an effect together with a video. Overrides the filter's setting |
audio |
string |
No | Public URL of an audio track to use instead of the video's own audio. Overrides the filter's audio |
resolution_mp |
number |
No | 0.7, 0.9, 1.5 or 2.0. Falls back to the filter's quality, then 1.5 |
aspect_ratio |
string |
No (default "auto") |
"auto" takes the filter's ratio, otherwise "16:9". Or set one of the fixed ratios from Shared parameters |
video_filter_id |
string |
No | UUID of a reference_to_video Video Filter |
email |
string |
No | Finished video link is emailed here once |
Response — 200 OK
{
"id": "r2v-5d0b7e3a1c48",
"status": "IN_QUEUE",
"output": {}
}
Poll POST /v1/status/reference_to_video/{job_id} with the id.
Errors
| HTTP | error_code |
Meaning |
|---|---|---|
400 |
VIDEO_FILTER_WRONG_MODE |
The Video Filter is not a reference_to_video filter |
404 |
VIDEO_FILTER_NOT_FOUND |
No Video Filter with that video_filter_id |
422 |
INSUFFICIENT_CREDITS |
Not enough credits for this request |
422 |
— | Validation error: duration outside 5–15, more than 2 reference_images, or prompt missing on a direct call |
4. Check Job Status
Each mode has its own status endpoint. Use the one matching the run endpoint you called:
| Run endpoint | Status endpoint |
|---|---|
/v1/run/text_to_video |
POST /v1/status/text_to_video/{job_id} |
/v1/run/image_to_video |
POST /v1/status/image_to_video/{job_id} |
/v1/run/reference_to_video |
POST /v1/status/reference_to_video/{job_id} |
The status endpoints are non-blocking — they return the current state immediately. Keep polling until the job reaches a terminal status.
Recommended polling interval: every 5–10 seconds. Video jobs typically take 1–3 minutes; longer and higher-resolution videos take longer. A job that has not finished after 6 minutes of generation is marked failed.
Path parameter
| Parameter | Type | Description |
|---|---|---|
job_id |
string |
The id returned from the run endpoint (t2v-…, i2v-… or r2v-…) |
Response — in progress
{
"id": "i2v-8c21e4a90b7f",
"status": "pending",
"output": null
}
Response — completed
{
"id": "i2v-8c21e4a90b7f",
"status": "completed",
"output": {
"video_url": "https://..."
}
}
Response — failed
{
"id": "i2v-8c21e4a90b7f",
"status": "failed",
"output": null
}
Status values
| Status | Terminal | Meaning |
|---|---|---|
pending |
No | Job is queued or actively generating (including any face swaps) |
completed |
Yes | Video is ready — output.video_url contains the download link |
failed |
Yes | Generation failed or timed out — stop polling. Credits are not refunded automatically; contact support if a job fails repeatedly |
Note: The run endpoints answer with
"status": "IN_QUEUE", while the status endpoints report the job state in lowercase (pending,completed,failed). Compare statuses case-insensitively to be safe.
End-to-end example (JavaScript)
const TOKEN = 'your_token_here';
const BASE = 'https://api.phantaisia.com';
async function generateVideo(mode, body) {
// 1. Submit job
const runRes = await fetch(`${BASE}/v1/run/${mode}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TOKEN}`,
},
body: JSON.stringify(body),
});
if (!runRes.ok) throw new Error(`Submit failed: ${await runRes.text()}`);
const { id } = await runRes.json();
console.log('Job submitted:', id);
// 2. Poll for result on the matching status endpoint
while (true) {
await new Promise(r => setTimeout(r, 7000));
const statusRes = await fetch(`${BASE}/v1/status/${mode}/${id}`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${TOKEN}` },
});
if (!statusRes.ok) throw new Error(`Status error: ${await statusRes.text()}`);
const data = await statusRes.json();
const status = String(data.status).toLowerCase();
console.log('Status:', status);
if (status === 'completed') {
return data.output.video_url;
}
if (status === 'failed') {
throw new Error('Generation failed');
}
}
}
// Text to video (direct)
const introUrl = await generateVideo('text_to_video', {
prompt: 'A hot air balloon drifts over a misty valley at sunrise.',
duration: 8,
resolution_mp: 1.5,
aspect_ratio: '16:9',
});
// Image to video with a Video Filter (face swap + animation)
const guestUrl = await generateVideo('image_to_video', {
video_filter_id: 'uuid-of-video-ai-filter',
duration: 8,
img: 'https://example.com/selfie.jpg',
gender: 1,
email: 'guest@example.com', // optional: emails the link when ready
});
// Reference to video (direct)
const refUrl = await generateVideo('reference_to_video', {
prompt: 'The subject walks through a neon-lit street.',
duration: 8,
resolution_mp: 1.5,
aspect_ratio: '9:16',
prompt_image: 'https://example.com/selfie.jpg',
video: 'https://example.com/reference.mp4',
include_video_audio: true,
});
Migrating from single_frame_video / first_last_frame_video
| Previous endpoints | New endpoints |
|---|---|
/v1/run/single_frame_video with prompt_image |
/v1/run/image_to_video with first_frame |
/v1/run/first_last_frame_video with first_frame + last_frame |
/v1/run/image_to_video with first_frame + last_frame |
video_filter_id + prompt_image / first_frame (selfie) |
video_filter_id + img (selfie) |
model: simple / advanced / performance |
No model field. Quality and price tier come from the Video Filter's video_model; direct calls are billed at the quick rate |
duration: "4s" / "6s" / "8s" |
duration: integer 5–15 |
| Fixed resolution | resolution_mp: 0.7 / 0.9 / 1.5 / 2.0 |
aspect_ratio only on performance |
aspect_ratio on every request, 8 ratios |
| Flat price per model and duration | Per-second pricing, see Pricing |
/v1/status/single_frame_video/{job_id} for both |
One status endpoint per mode |