Videos are made in the background: send the request, check the status until it is done, then download the file. The API keeps the shape of OpenAI's Videos API and adds frame ratio, resolution and first/last frames.
Key points
- Find video models with GET /v1/models: they list videos.generations in endpoints.
- POST /v1/videos returns an id at once; the status moves queued → in_progress → completed or failed.
- Download with GET /v1/videos/{id}/content; a video is kept until expires_at.
- Price = base price × length weight × resolution weight; you are charged only once the video is saved.
- A failed, cancelled or timed-out video is never charged.
How it works#
Videos take time, so the API works as a background task, like OpenAI's Videos API:
- Create:
POST /v1/videosreturns a video object at once,queued, with itsid. - Follow: call
GET /v1/videos/{id}every 10–15 seconds.statusmoves toin_progress, thencompletedorfailed. - Download: once
completed, fetch the file withGET /v1/videos/{id}/content.
A video usually takes 1–3 minutes. One not done within the time limit (currently 20 minutes) stops with VIDEO_TIMEOUT, and you are not charged.
Make your first video#
The code below does all three steps: it makes an 8-second 16:9 video at 720p, waits for it, then saves it as boat.mp4. Take VIDEO_MODEL_ID from GET /v1/models: video models list videos.generations in endpoints.
# 1. Create the video
VIDEO_ID=$(curl -s https://api.xpncore.com/v1/videos \
-H "Authorization: Bearer $XPNCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "VIDEO_MODEL_ID",
"prompt": "A paper boat drifting down a rainy neon street, slow dolly shot",
"seconds": "8",
"size": "1280x720"
}' | jq -r '.id')
# 2. Check every 10 seconds until it is completed or failed
while true; do
STATUS=$(curl -s https://api.xpncore.com/v1/videos/$VIDEO_ID \
-H "Authorization: Bearer $XPNCORE_API_KEY" | jq -r '.status')
echo "$STATUS"
case "$STATUS" in completed|failed) break ;; esac
sleep 10
done
# 3. Download the file
curl -L https://api.xpncore.com/v1/videos/$VIDEO_ID/content \
-H "Authorization: Bearer $XPNCORE_API_KEY" \
-o boat.mp4{
"id": "4c102af4-5d04-444c-846e-05f1eefccf7b",
"object": "video",
"model": "VIDEO_MODEL_ID",
"status": "queued",
"progress": 0,
"created_at": 1759550400,
"completed_at": null,
"expires_at": null,
"seconds": "8",
"size": "1280x720",
"prompt": "A paper boat drifting down a rainy neon street, slow dolly shot",
"remixed_from_video_id": null,
"error": null
}Create parameters#
Send JSON, or multipart/form-data when you attach image files. The body is limited to 48 MB. An option the model does not offer is refused with a 422 VIDEO_OPTION_UNSUPPORTED that says why.
modelstringRequiredA video model ID from
GET /v1/models.promptstringRequiredWhat the video shows, up to 4,000 characters: subject, action, setting, camera movement, sound.
secondsstring | integerOptionalLength, for example
"4","6","8".durationworks too. Defaults to the model's own (usually 8).sizestringOptionalWIDTHxHEIGHT, for example1280x720(16:9, 720p) or720x1280(9:16, 720p). You can sendaspect_ratioandresolutioninstead.aspect_ratiostringOptionalFrame ratio, for example
16:9or9:16.resolutionstringOptionalFor example
720p,1080p,4k. Higher resolutions usually come at 8 seconds only.input_referencefile | stringOptionalAn image to start from (see Video from an image). Also accepted as
first_frameorimage.last_framefile | stringOptionalAn image to end on; needs a first frame.
reference_imagesarrayOptionalUp to 3 reference images for a character or style. Not combined with first or last frames, and usually at 8 seconds only.
negative_promptstringOptionalWhat should not appear, up to 1,000 characters, when the model supports it.
seedintegerOptional0 to 4294967295, for more repeatable results, when the model supports it.
Ratio, length and resolution#
size becomes a frame ratio and a resolution: the shorter side is the resolution.
1280x720→ 16:9, 720p ·720x1280→ 9:16, 720p1920x1080→ 16:9, 1080p ·1080x1920→ 9:16, 1080p3840x2160→ 16:9, 4k
Each model offers its own choices. The most common: 16:9 and 9:16; 4, 6 or 8 seconds; 720p at any length, 1080p and 4k at 8 seconds only. Leave the fields out and the model uses its defaults (usually 16:9, 8 seconds, 720p). Most models make videos with sound.
Video from an image#
Send an image in input_reference and the video starts from exactly that frame. The image can be:
- a file part in multipart (as in the sample),
- a data URL
data:image/png;base64,...or a base64 string in a JSON body, - a public
https://link (fetched within 20 seconds).
It must be PNG, JPEG or WebP, up to 10 MB. Match the video's frame ratio for the best result. Add last_frame to choose the closing frame as well.
curl https://api.xpncore.com/v1/videos \
-H "Authorization: Bearer $XPNCORE_API_KEY" \
-F model=VIDEO_MODEL_ID \
-F prompt="The boat slowly sails away as the rain stops" \
-F seconds=8 \
-F size=1280x720 \
-F [email protected]Follow the status#
The video object keeps the shape of OpenAI's Videos API:
queued: waiting its turn.in_progress: being made;progressgives the percentage (0–100).completed: done; the file can be downloaded untilexpires_at.failed: it could not be made;error.codeanderror.messagesay why. A failed video is not charged.
Times are Unix seconds. Check every 10–15 seconds; checking more often does not make the video faster.
{
"id": "4c102af4-5d04-444c-846e-05f1eefccf7b",
"object": "video",
"model": "VIDEO_MODEL_ID",
"status": "completed",
"progress": 100,
"created_at": 1759550400,
"completed_at": 1759550492,
"expires_at": 1760155292,
"seconds": "8",
"size": "1280x720",
"prompt": "A paper boat drifting down a rainy neon street, slow dolly shot",
"remixed_from_video_id": null,
"error": null
}Download the video#
GET /v1/videos/{id}/content returns the video file itself (usually video/mp4) and supports the Range header, so it can be played or resumed from the middle.
- A video is kept until
expires_at(currently 7 days after it is made), then deleted. Download and keep your own copy for longer use. - Not ready yet: 404
VIDEO_NOT_READY. Deleted or expired: 404VIDEO_GONE. - Videos made through the API and in Chat AI › Videos share one store per account (currently 2 GB).
List and delete videos#
GET /v1/videoslists your videos, newest first. Parameters:limit(1–100, default 20),after(the last id of the previous page),order(descorasc). The response hasdata,first_id,last_idandhas_more.DELETE /v1/videos/{id}: a video stillqueuedis cancelled and refunded; a finished one has its file deleted to free space. A video being made cannot be cancelled (409VIDEO_NOT_CANCELLABLE).
# The 20 newest videos
curl "https://api.xpncore.com/v1/videos?limit=20" \
-H "Authorization: Bearer $XPNCORE_API_KEY"
# The next page: the videos after the last id of this one
curl "https://api.xpncore.com/v1/videos?limit=20&after=LAST_ID" \
-H "Authorization: Bearer $XPNCORE_API_KEY"
# Delete a video (a queued one is cancelled and refunded)
curl -X DELETE https://api.xpncore.com/v1/videos/$VIDEO_ID \
-H "Authorization: Bearer $XPNCORE_API_KEY"Pricing and billing#
You pay per video: base price × length weight × resolution weight. A 4-second video costs less than an 8-second one, and 1080p or 4k costs more than 720p.
- On create, the video's price is held in your wallet.
- You are charged only once the file is saved, never more than the hold.
- A failed, cancelled or timed-out video releases the whole hold.
Each model's base price is on the Models & pricing page; the exact price of every choice shows in Chat AI › Videos before you make one.
Common errors#
Refusals on create (nothing is held):
- 402
INSUFFICIENT_FUNDS: the wallet balance does not cover the video. - 409
VIDEO_TOO_MANY_ACTIVE: the most videos you can make at once (currently 3) are in progress; wait for one to finish. - 409
VIDEO_STORAGE_FULL: your video store is full; delete older videos. - 422
VIDEO_OPTION_UNSUPPORTED: the model does not offer that ratio, length, resolution or image input. - 400
CONTENT_POLICY_BLOCKED: the prompt breaks the content policy. - 503
MODEL_PRICING_UNAVAILABLE: the model has no price yet and cannot be used.
Codes in error.code of a failed video (never charged):
VIDEO_CONTENT_BLOCKED: the safety filter refused the content; rewrite the prompt or change the image.VIDEO_TIMEOUT: not done within the time limit.VIDEO_UPSTREAM_FAILED,VIDEO_NO_CAPACITY: the model is busy or unavailable; retry in a few minutes.VIDEO_DOWNLOAD_FAILED,VIDEO_TOO_LARGE,VIDEO_NOT_VIDEO: the result could not be fetched or was not valid.VIDEO_CANCELLED: you cancelled it.
Compatible endpoint#
Besides /v1/videos, the gateway takes POST /v1/video/generations and GET /v1/video/generations/{id} for tools that expect that shape. They take the same parameters, but the object has object: "video.generation" and the statuses queued, processing, succeeded, failed. When done, video_url points to /v1/videos/{id}/content (API key required).
To try ideas without code, or to extend a video you made, use Chat AI › Videos in the dashboard.