DocsOpen Moon AI
Browse the docs

Videos

Reference for POST /v1/videos and the GET /v1/videos routes: start a video, poll the job, download it and list your videos, with timeouts and costs.

On this page

A video takes several minutes. Starting one returns a job right away; you then poll the job and download the video once it is finished. The endpoints follow the OpenAI video API.

MethodPathPurpose
POST/v1/videosStart a video
GET/v1/videos/{id}Check on a video
GET/v1/videos/{id}/contentDownload the video
GET/v1/videosList your videos

A job goes through the statuses queued, in_progress, then completed or failed. It belongs to the workspace of the key that started it: any key from that workspace can check on it and download it.

Full exampleLink to the Full example section

cURL
# 1. Start the video
curl https://api.realmoon.ai/v1/videos \
  -H "Authorization: Bearer $MOONAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "alibaba/wan-2.6", "prompt": "A wave at sunset", "seconds": "5"}'
# => {"id": "video_3c9e…", "status": "queued", …}

# 2. Poll the job (about every 10 seconds)
curl https://api.realmoon.ai/v1/videos/video_3c9e... \
  -H "Authorization: Bearer $MOONAI_API_KEY"

# 3. Download the video once it is "completed"
curl https://api.realmoon.ai/v1/videos/video_3c9e.../content \
  -H "Authorization: Bearer $MOONAI_API_KEY" -o wave.mp4
Python
import os
import time
from openai import OpenAI

client = OpenAI(base_url="https://api.realmoon.ai/v1", api_key=os.environ["MOONAI_API_KEY"])

job = client.videos.create(
    model="alibaba/wan-2.6",
    prompt="A wave at sunset",
    seconds="5",
    extra_body={"resolution": "720p", "aspect_ratio": "16:9"},
)
while job.status in ("queued", "in_progress"):
    time.sleep(10)
    job = client.videos.retrieve(job.id)

if job.status == "completed":
    client.videos.download_content(job.id).write_to_file("wave.mp4")
else:
    print("Failed:", job.error)
JavaScript
import fs from 'node:fs';
import OpenAI from 'openai';

const client = new OpenAI({ baseURL: 'https://api.realmoon.ai/v1', apiKey: process.env.MOONAI_API_KEY });

let job = await client.videos.create({
  model: 'alibaba/wan-2.6',
  prompt: 'A wave at sunset',
  seconds: '5',
  resolution: '720p', // Moon AI-specific parameter, sent as is
});
while (job.status === 'queued' || job.status === 'in_progress') {
  await new Promise((r) => setTimeout(r, 10_000));
  job = await client.videos.retrieve(job.id);
}

if (job.status === 'completed') {
  const content = await client.videos.downloadContent(job.id);
  fs.writeFileSync('wave.mp4', Buffer.from(await content.arrayBuffer()));
} else {
  console.error('Failed:', job.error);
}

Start a videoLink to the Start a video section

HTTP
POST https://api.realmoon.ai/v1/videos
  • Authentication: an sk-moon-… key with the inference scope (see Authentication).
  • Body: JSON, or multipart/form-data (with the Content-Length header) to send the first and last frames as files. 60 MB at most.
  • Response: the job’s video object, with status queued, and the X-Moon-Request-Id header, which you use to read the cost from /v1/generation.

ParametersLink to the Parameters section

ParameterTypeRequiredDescription
modelstringyesA video model from GET /v1/models?type=video.
promptstringyesDescription of the video, 4,000 characters at most.
seconds (or duration)integer or stringnoDuration in seconds, one of the model’s durations. Default: the first duration of at least 5 seconds, otherwise the longest.
resolutionstringnoOne of the model’s resolutions (480p, 720p, 1080p, 4K…). Default: 720p when the model offers it, otherwise its first resolution.
sizestringnoWIDTHxHEIGHT, for example 1280x720: the closest resolution and aspect ratio are derived from it, if you don’t give them.
aspect_ratiostringnoOne of the model’s aspect_ratios (16:9, 9:16…). Default: the first one in the list.
generate_audiobooleannoWith or without sound, for models that produce sound (audio: true). Default: with sound if the model produces it. true on a silent model: 400 unsupported_value.
seedintegernoSeed, from 0 to 2,147,483,647.
input_reference, image or first_frameimagenoFirst frame: an https:// URL, a base64 data: URL, bare base64, or a multipart file. For models where first_frame is true.
last_frameimagenoLast frame, together with a first frame, for models where last_frame is true.
userstringnoAccepted, with no effect.

Any other parameter is rejected with 400 unknown_parameter. An image is 20 MB at most (PNG, JPEG, WebP or GIF). An image at an https:// URL is fetched by the model provider, not by Moon AI.

ExampleLink to the Example section

cURL
curl https://api.realmoon.ai/v1/videos \
  -H "Authorization: Bearer $MOONAI_API_KEY" \
  -F model=kwaivgi/kling-v3.0-std \
  -F prompt="The photo comes to life: the clouds drift by" \
  -F seconds=5 \
  -F generate_audio=false \
  -F first_frame=@landscape.png
Python
job = client.videos.create(
    model="kwaivgi/kling-v3.0-std",
    prompt="The photo comes to life: the clouds drift by",
    seconds="5",
    input_reference=open("landscape.png", "rb"),
    extra_body={"generate_audio": False},
)
print(job.id, job.status)
JavaScript
const job = await client.videos.create({
  model: 'kwaivgi/kling-v3.0-std',
  prompt: 'The photo comes to life: the clouds drift by',
  seconds: '5',
  input_reference: fs.createReadStream('landscape.png'),
  generate_audio: false, // Moon AI-specific parameter, sent as is
});
console.log(job.id, job.status);

The video objectLink to the The video object section

POST /v1/videos, GET /v1/videos/{id} and GET /v1/videos return video objects.

Completed video object
{
  "id": "video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b",
  "object": "video",
  "model": "alibaba/wan-2.6",
  "status": "completed",
  "progress": 100,
  "created_at": 1793577600,
  "completed_at": 1793577780,
  "seconds": "5",
  "size": "1280x720",
  "resolution": "720p",
  "aspect_ratio": "16:9",
  "generate_audio": true,
  "remixed_from_video_id": null,
  "error": null,
  "content_url": "/v1/videos/video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b/content",
  "expires_at": 1793581380,
  "usage": { "seconds": 5, "credits": 2800 }
}
FieldTypeDescription
idstringThe job ID: video_ followed by 32 characters.
objectstringAlways video.
modelstringThe model you called.
statusstringqueued, in_progress, completed or failed.
progressintegerProgress from 0 to 100, when the provider reports it; 100 once finished.
created_at, completed_atintegerTimestamps in Unix seconds. completed_at is null until the job finishes.
secondsstringThe duration requested from the model.
sizestringThe dimensions that match the resolution and aspect ratio, for example 1280x720.
resolution, aspect_ratio, generate_audiostring, string, booleanWhat was requested from the model.
remixed_from_video_idnullAlways null, kept for compatibility.
content_urlstringOnce finished: the download URL.
expires_atintegerOnce finished: the time until which the video stays available (about one hour after it finishes).
errorobject or nullOn failure: {"code", "message"}.
usageobjectOnce the job is settled: seconds (duration) and credits (credits charged).

error.code values for a failed job, which is never charged:

codeCause
generation_failedThe model could not make the video.
content_policy_violationThe model’s safety system rejected the request.
timeoutThe video was not finished after 6 hours and was abandoned.
not_foundThe provider no longer knows about the job.

Check on a videoLink to the Check on a video section

HTTP
GET https://api.realmoon.ai/v1/videos/{id}

Returns the up-to-date video object.

  • Authentication: an sk-moon-… key from the workspace that started the video, with the inference scope.
  • Path parameter: id, the job’s video_… ID.
  • Poll the job about every 10 seconds: Moon AI does not ask the provider for the status more than once every 5 seconds.
  • Reading the job is not billed.
cURL
curl https://api.realmoon.ai/v1/videos/video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b \
  -H "Authorization: Bearer $MOONAI_API_KEY"
Python
job = client.videos.retrieve("video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b")
print(job.status, job.progress)
JavaScript
const job = await client.videos.retrieve('video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b');
console.log(job.status, job.progress);

Download the videoLink to the Download the video section

HTTP
GET https://api.realmoon.ai/v1/videos/{id}/content

Sends the video file, usually video/mp4, with a Content-Disposition header that suggests the name <id>.mp4.

  • Authentication: an sk-moon-… key from the workspace that started the video, with the inference scope.
  • Before the job finishes: 409 video_not_ready.
  • Moon AI does not keep the video: it is read from the provider at download time. Download it within an hour of completion and keep your own copy. When the provider no longer has it: 410 video_expired.
  • At most 4 downloads at once per workspace (beyond that: 429 too_many_concurrent).
  • Downloading is not billed.
cURL
curl https://api.realmoon.ai/v1/videos/video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b/content \
  -H "Authorization: Bearer $MOONAI_API_KEY" -o wave.mp4
Python
client.videos.download_content("video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b").write_to_file("wave.mp4")
JavaScript
const content = await client.videos.downloadContent('video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b');
fs.writeFileSync('wave.mp4', Buffer.from(await content.arrayBuffer()));

List your videosLink to the List your videos section

HTTP
GET https://api.realmoon.ai/v1/videos

Lists the workspace’s videos, most recent first.

ParameterTypeRequiredDescription
limitintegernoNumber of videos per page, clamped between 1 and 100. 20 by default.
afterstringnoThe ID of a video in the workspace: the page starts after it.
cURL
curl "https://api.realmoon.ai/v1/videos?limit=10" \
  -H "Authorization: Bearer $MOONAI_API_KEY"
Python
# The SDK fetches the next pages as you iterate.
for video in client.videos.list(limit=10):
    print(video.id, video.status)
JavaScript
// The SDK fetches the next pages as you iterate.
for await (const video of client.videos.list({ limit: 10 })) {
  console.log(video.id, video.status);
}
Response
{
  "object": "list",
  "data": [{ "id": "video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b", "object": "video", "status": "completed" }],
  "first_id": "video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b",
  "last_id": "video_3c9e2f6a8b1d4e7f9a0b2c4d6e8f1a3b",
  "has_more": false
}
FieldTypeDescription
objectstringAlways list.
dataarrayThe page’s video objects (shortened in the example).
first_id, last_idstring or nullFirst and last ID on the page.
has_morebooleantrue if there are more videos: pass last_id in after to get the next page.

ModelsLink to the Models section

List as of November 5, 2026; the current list, with prices per second, comes from GET /v1/models?type=video.

ModelDurations (s)ResolutionsAspect ratiosSoundFirst / last frame
alibaba/wan-2.65, 10480p, 720p, 1080p16:9, 9:16yesyes / no
alibaba/wan-2.72 to 10720p, 1080p16:9, 9:16, 1:1, 4:3, 3:4yesyes / yes
alibaba/wan-3.02 to 15480p, 720p, 1080p16:9, 9:16, 1:1, 4:3, 3:4yesyes / yes
kwaivgi/kling-v3.0-std, kling-v3.0-pro3 to 15720p, 1080p16:9, 9:16, 1:1yes, or without (cheaper)yes / yes
google/veo-3.1-lite4, 6, 8720p, 1080p16:9, 9:16yes, or without (cheaper)yes / yes
google/veo-3.1-fast, google/veo-3.14, 6, 8720p, 1080p, 4K16:9, 9:16yes, or without (cheaper)yes / yes
minimax/hailuo-2.36, 101080p16:9noyes / no
x-ai/grok-imagine-video1 to 15480p, 720p16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3noyes / no
runway/gen-4.52 to 10720p16:9, 9:16noyes / no

CostsLink to the Costs section

  • When it starts, the job holds the price of the requested duration, with a small margin, plus the first and last frames for models that charge for them.
  • A finished video is charged at the actual cost reported by the provider (or, failing that, the list price), and the rest of the hold is released.
  • A video that fails, is refused, is abandoned after 6 hours, or whose start was cut off by our timeout costs nothing.
  • The hold of a job in progress counts against the balance and the spend caps until the job ends.

See Costs and wallet.

LimitsLink to the Limits section

  • At most 20 videos in progress at once per workspace (beyond that: 429 too_many_concurrent, Retry-After: 30).
  • Starting a video responds within about one minute at most.
  • Moon AI keeps the job’s state (status, duration, resolution, aspect ratio, cost), not your description or the video.

ErrorsLink to the Errors section

HTTPcodeEndpointMeaning
400missing_required_parameterstartmodel or prompt is missing.
400unknown_parameterstartUnknown parameter.
400unsupported_valuestartDuration, resolution, aspect ratio, size or generate_audio outside what the model accepts.
400unsupported_parameterstartA first or last frame on a model that does not accept it, or a parameter the model rejects (with a hint).
400string_above_max_lengthstartDescription longer than 4,000 characters.
400invalid_imagestartUnreadable image, wrong type, or too large.
400content_policy_violationstartThe model’s safety system rejected the request.
400nullstart, listInvalid seed or generate_audio, last frame without a first frame, invalid base64, unknown after.
403org_policystartYour organization’s rules refuse this model.
404model_not_foundstartUnknown model, or one that does not serve this endpoint.
404video_not_foundcheck, downloadNo video in this workspace has this ID.
409video_not_readydownloadThe video is not finished yet.
410video_expireddownloadThe video is no longer available at the provider.
411length_requiredstartMultipart upload without a Content-Length header.
413request_too_large, file_too_largestartBody or image too large.
429too_many_concurrentstart, downloadToo many videos in progress, or downloads, in the workspace.
429upstream_rate_limitedstartThe model is overloaded: retry after Retry-After (10 seconds).
502upstream_errorstart, downloadThe model failed, or the video could not be read: try again in a moment.
503job_not_recordedstartThe video was started but could not be recorded: contact support.
504upstream_timeoutstartThe model did not respond in time when starting. Nothing is charged.

Key, balance and spend cap errors are in Errors.

See alsoLink to the See also section

  • Models: the durations, resolutions, audio, first_frame and last_frame fields.
  • Limits: retry without starting two videos.

Moon AI

Moon AI in action

0:00

Explore

Screenshots of chat.realmoon.ai taken on October 29, 2026, with fictional data.

Moon AI

Your preferences

Visits counted without cookies.

Our server counts visits without cookies or trackers. Your theme and your answer about ad measurement are remembered on this device.

Ad measurement

With your consent, our ad networks’ trackers (Google) measure our ads, from the visit to the sign-up and then to the first subscription and its amount, and may show you our ads. Your answer is valid for 13 months; you can change it here at any time.

No answer saved on this device.

After you decline, no tag is loaded anymore and the site deletes the cookies they had set on realmoon.ai. Those on the ad networks’ own domains are managed with them or in your browser.

The Moon AI app has its own privacy settings.