Create video
Submit a video generation task. The request returns 202 Accepted immediately with a task id and a polling URL — generation runs asynchronously. Poll the task or receive a webhook, then read the result with Get video status.
POST https://api.ofox.ai/v1/videosRequest parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | ✅ | Model slug, e.g. google/veo-3.1 or bytedance/seedance-2.0 |
prompt | string | ✅ | Text description of the video |
duration | integer | — | Video length in seconds |
resolution | string | — | 480p / 720p / 1080p / 1K / 2K / 4K |
aspect_ratio | string | — | 16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 3:2 / 2:3 / 21:9 / 9:21 |
size | string | — | Exact pixels WIDTHxHEIGHT (e.g. 1280x720), an alternative to resolution + aspect_ratio |
frame_images | array | — | Frame control (image-to-video / first-and-last frame). Present ⇒ treated as image-to-video. See below |
input_references | array | — | Guide subject / style (reference-to-video), not precise frame anchors. See below |
generate_audio | boolean | — | Defaults to true on models that support audio output |
seed | integer | — | Deterministic generation seed (not guaranteed on all providers) |
callback_url | string | — | Terminal-state webhook URL, must be HTTPS (see Webhooks) |
provider | object | — | Provider-specific passthrough, see Provider passthrough |
frame_images[] element
Each element pins one frame of the output. frame_type is first_frame or last_frame.
{
"type": "image_url",
"image_url": { "url": "https://example.com/first.jpg" },
"frame_type": "first_frame"
}input_references[] element
Pass image / audio / video references by type — these guide subject, style, and consistency, not precise frame anchors:
{ "type": "image_url", "image_url": { "url": "https://example.com/subject.jpg" } }
{ "type": "audio_url", "audio_url": { "url": "https://example.com/voice.mp3" } }
{ "type": "video_url", "video_url": { "url": "https://example.com/ref.mp4" } }| Media | type | Max count | Other constraints |
|---|---|---|---|
| Image | image_url | ≤ 9 | — |
| Audio | audio_url | ≤ 3 | ≤ 15s per clip |
| Video | video_url | ≤ 1 | — |
Exceeding any limit returns 400 too_many_references (see Errors).
frame_images and input_references are mutually exclusive — a single request uses one or the other, never both. Sending both returns 400 references_conflict.
Generation mode (implicit)
There is no gen_mode field. The mode is inferred from which inputs you send:
| Mode | Detected when |
|---|---|
text2video | Neither frame_images nor input_references |
img2video | frame_images with one first_frame |
img2video_end | frame_images with first_frame + last_frame |
imgref2video | input_references, images only |
mixref2video | input_references mixing image / audio / video |
Response
202 Accepted — the task is accepted with status: "pending" and a polling_url:
{
"id": "9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718",
"status": "pending",
"polling_url": "https://api.ofox.ai/v1/videos/9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718"
}Poll polling_url (or wait for a webhook) until the task reaches a terminal state — see Get video status.
Request examples
One /v1/videos schema covers every generation mode; the mode is chosen implicitly from your inputs. Each example below runs as-is.
Text to video
No frame_images / input_references → text-to-video.
curl -X POST https://api.ofox.ai/v1/videos \
-H "Authorization: Bearer $OFOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/veo-3.1",
"prompt": "A golden retriever running on the beach at sunset",
"duration": 5,
"resolution": "1080p",
"aspect_ratio": "16:9",
"generate_audio": true
}'Stackable options
The fields below are orthogonal to the generation mode — add any of them to any request body above.
Webhook (callback_url)
Receive a POST at your endpoint when the task reaches a terminal state instead of polling.
{
"model": "google/veo-3.1",
"prompt": "...",
"callback_url": "https://your-app.example/webhooks/ofox-video"
}callback_url must be HTTPS. See Webhooks for the payload and signature.
Provider passthrough
Provider-specific parameters go through provider.options.<slug> — only the matched provider receives them. Parameters like watermark aren’t supported by every model, so they live in provider passthrough rather than as top-level fields (this follows OpenRouter’s provider-passthrough convention). Which passthrough options a model accepts depends on the model’s capabilities.
{
"model": "alibaba/wan-2.7",
"prompt": "...",
"provider": {
"options": {
"dashscope": { "watermark": false, "audio_setting": "..." }
}
}
}