Skip to content

Video API

gmlx serve answers the OpenAI video routes with the video models of its configuration. This page lists the routes, the fields a request takes, the job it makes and the errors it can get, for an app or a script that makes clips through the server. A first clip:

curl -s localhost:8080/v1/videos -H "Content-Type: application/json" \
  -d '{"model": "h3", "prompt": "waves on a pebble beach at dawn", "seconds": "5", "size": "768x768", "seed": 7}'
# {"id": "video_3f...", "object": "video", "status": "queued", ...}
curl -s localhost:8080/v1/videos/video_3f...
curl -s -o beach.mp4 localhost:8080/v1/videos/video_3f.../content

A clip takes minutes, so POST /v1/videos answers at once with a job, which runs in the background. You read the job until it is completed, then download its MP4.

A server whose configuration has a video model serves these routes. They need ffmpeg, which writes the MP4, as Video generation describes.

Routes

Route Body Does
POST /v1/videos JSON or multipart form Queues a job that makes a clip, and answers with the job
GET /v1/videos/{id} None The job: its status, progress and error
GET /v1/videos/{id}/content None The MP4 of a completed job
DELETE /v1/videos/{id} None Cancels a queued or running job, or removes a finished one and its MP4

Request fields

Field Meaning
prompt What the clip shows and sounds like. Required.
model A video_models id. It may be left out when the configuration has one video model.
seconds The length, up to 15, as a number or a string such as "8", rounded up to whole frames. Unset, the entry's, else 5.
size WxH in multiples of 32. Unset, the entry's defaults.size, else 1344x768.
seed The seed of the noise, an integer under 2^32, random without it.
prompt_upsampling false runs the prompt as typed on an entry with an upsampler, and true rewrites it on one with upsample: false.
first_frame, input_reference An image the clip starts at. input_reference is the OpenAI name of the same field.
last_frame An image the clip ends at.
guides Images, videos and sounds anchored at frames of the clip, in a multipart form.
guide_no_audio true anchors guide videos without their sound.
int8 off, attention, linears or all, the int8 setting of the DiT. Unset, the entry's defaults.int8, else off.

A value that changes the entry's linear layers gets 400, as In serve and ComfyUI explains.

The steps and the shifts come from the entry, as on the command line. A references list asks for MiniMax-H3's Ref2VA mode, which the server does not run, and gets 400. Other fields, adapters among them, are ignored, and the log names them in a warning.

Keyframes and guides

A JSON body takes first_frame and last_frame as image data URLs, data:image/png;base64,.... The server never reads a local path or fetches a URL. Guides need a multipart form. Each guide is a file field, and the guides field lists them as JSON, each with its field name, the frame it starts at, from the end when below 0, and its kind, video for an image or a video with its sound, or audio for a sound alone:

curl -s localhost:8080/v1/videos \
  -F model=h3 -F prompt="the fox turns and runs into the snow" \
  -F first_frame=@fox.png -F guide_0=@steps.wav \
  -F 'guides=[{"file": "guide_0", "frame": 48, "kind": "audio"}]'

The anchors work as --first-frame, --last-frame, --guide and --guide-audio do, as Keyframes and guides describes, and take the memory listed there. A guide video is read a few seconds at a time when its whole encode does not fit beside the models loaded. FastH3 runs text to video only, so it refuses them.

The job

The job is the OpenAI video object, with a gmlx part:

{
  "id": "video_3f...",
  "object": "video",
  "model": "h3",
  "status": "in_progress",
  "progress": 47,
  "created_at": 1762400000,
  "completed_at": null,
  "expires_at": 1762486400,
  "seconds": "5.17",
  "size": "768x768",
  "prompt": "waves on a pebble beach at dawn",
  "error": null,
  "gmlx": {"frames": 124, "seed": 7, "steps": 8, "int8": "off",
           "step": 4, "phase": "denoising", "revised_prompt": null,
           "eta_s": 210}
}

status runs from queued to in_progress, and ends completed, failed or cancelled. seconds is the clip's length, its frames over 24. progress is 0 until the prompt encode, 5 from it, counts the steps to 90 and the decode after them, and is 100 when the MP4 is written. gmlx.phase names the work now running, gmlx.step the steps done, and gmlx.eta_s the seconds the job expects to take still. revised_prompt holds the upsampler's caption, and gmlx.int8 the int8 setting the DiT runs at.

Jobs run one at a time, in order, on a thread of their own, so a clip never holds up the image routes. Chat replies keep streaming while a clip runs, since the clip pauses for them. On an M5 Max, beside a chat reply that streams without a break, the steps of a 768x768 clip of 22 frames took about 2.7 times as long as alone at the default share, and about 1.6 times at GMLX_IMAGE_CHAT_SHARE 0, as Video jobs describes. When the memory a job needs is held by models in use, the job stays in_progress with gmlx.phase waiting for memory, and tries again once they finish. A failed job has error with its code, message and the HTTP status the request would have had. A job still queued when it expires never runs. It fails with timed_out and keeps its record as any failed job does.

DELETE on a queued job cancels it at once. On a running job it answers at once, and the job stops at its next step or decoded piece. A cancelled job keeps its record until it expires, and a second DELETE removes it.

The job store

The jobs, their inputs and their MP4s live under ~/.cache/gmlx/videos/<port>/. A finished job and its MP4 last video.job_ttl_s seconds from when it finishes, a day by default, and then read as 404. Past video.store_max_bytes the oldest MP4s go first, and their jobs with them. The files of a job's form go to the store as they arrive, and so do the images of a JSON body's keyframes. A form with a Content-Length takes its room before its first byte, and one sent with no length takes it 64 MiB at a time as its bytes come. A job whose files do not fit in the room left gets 413, and the message says how much of the store the uploads arriving now hold. A server that restarts fails the jobs it had queued or running with server_restarted and status 500, and keeps the ones that ended.

Errors

Condition Response
The server cannot run ffmpeg 501 naming ffmpeg and brew install ffmpeg
More jobs queued than video.max_queue 429 queue_full, with a Retry-After of the median job time less the running job's time, at least 2, or 60 before the first clip ends
No model with several video models, or an unknown one 400 naming the video models, or 404 model_not_found
The entry's file is no video DiT, its chip, an adapter or its defaults are refused, or its default clip does not fit the working budget 400 video_model_mismatch
seconds past 15, or a size that is no multiple of 32 400 naming the field
A clip too large for the memory left beside the pinned models 413 with the plan's phases and what to change
A JSON body that its read and parse cannot hold in the memory free 413 naming both figures, before the body is parsed
A JSON body, or a form's text fields, that fits the memory free alone, but not beside the other bodies being read 503 with Retry-After, naming what those bodies hold
A form's text fields past the memory free 413 naming the memory free, once the text read passes it
A guides field whose parse cannot fit in the memory free 413 naming guides and both figures, before it is parsed
A form's files larger than the room left in the job store 413 naming both figures, before the files are read
A JSON body or a form that sends nothing for 60 s before its end 408, and a form frees the room it held in the job store
A JSON keyframe larger than the room left in the job store 413 naming the keyframe and both figures
A keyframe or guide image over 64 Mi pixels 400 naming the field
A guide video whose frames are over 64 Mi pixels The job fails with 400 naming the guide, before a frame is read
A guide video that does not fit in the clip from its frame The job fails with 400 naming the guide
A guide video whose encode does not fit beside the models loaded, even a few seconds at a time The job fails with 400 naming the guide: "... does not fit in memory beside the models now loaded: no window fits; the clip needs NEED, and FREE is free"
references 400 naming Ref2VA
An int8 off the list, one whose linear layers differ from the entry's, or one this Mac or install cannot run 400 naming int8, as in Int8 refusals
prompt_upsampling: true on an entry without an upsampler 400 naming the upsampler setting
An unknown or expired job 404 not_found
The content of a job not completed 409 not_ready
A clip that does not fit beside the models loaded when it runs The job fails with 400, as this clip does not fit in memory describes
Memory held by models in use past the job's expiry The job fails with timed_out
A job still queued at its expiry The job fails with timed_out, "the job was still queued when it expired"
The upsampler gives no caption for a job whose prompt it rewrites The job fails with 500 upsample_failed
The server restarted during the job The job fails with 500 server_restarted

Troubleshooting has the fix for each message.