Video-router image_url to /v1/videos frame_images, in Python

Map image_url, end_image_url and reference_image_urls from /v1/video-router/generate to frame_images and input_references on /v1/videos, with Python.

5 min readSume
All posts

To move a request from /v1/video-router/generate to /v1/videos, turn image_url into a frame_images entry with frame_type: "first_frame", end_image_url into one with "last_frame", and reference_image_urls into input_references. The legacy routes stay registered as an alias over the same jobs, so there is no deadline, but new integrations should use /v1/videos.

The mapping

Model ids are the same on both surfaces, and are bare ids such as wan-3.0. See the video generation docs and the legacy Video Router docs.

Legacy to /v1/videos field map, read 2026-10-05
Legacy field/v1/videos fieldMode it selects
image_urlframe_images[] with frame_type: first_frameImage-to-video
end_image_urlframe_images[] with frame_type: last_frameImage-to-video
reference_image_urlsinput_references[] with type: image_urlReference-to-video
video_url (edit, Recast, Genjutsu source)Stays a Video Router field; not in the OpenRouter-shaped bodyEdit or swap
model, prompt, duration, resolution, aspect_ratioSame namesNo change

A converter

This runs as written with Python 3 and prints the new body. It drops the legacy image fields and, because frame_images wins on /v1/videos, leaves references out when a frame is present.

def convert(old):
    new = {k: v for k, v in old.items()
           if k not in ("image_url", "end_image_url", "reference_image_urls")}
    frames = []
    if old.get("image_url"):
        frames.append(frame(old["image_url"], "first_frame"))
    if old.get("end_image_url"):
        frames.append(frame(old["end_image_url"], "last_frame"))
    if frames:
        new["frame_images"] = frames
    refs = old.get("reference_image_urls") or []
    if refs and not frames:
        new["input_references"] = [
            {"type": "image_url", "image_url": {"url": u}} for u in refs]
    return new

def frame(url, kind):
    return {"type": "image_url", "image_url": {"url": url}, "frame_type": kind}

legacy = {"model": "wan-3.0", "prompt": "Pan across the shelf",
          "duration": 6, "image_url": "https://example.com/a.png",
          "end_image_url": "https://example.com/b.png"}
print(convert(legacy))

Behaviors to expect

  • The mode is inferred from the body, so there is no separate mode field to carry over.
  • size, seed and a non-empty provider.options return 400 unsupported_parameter on /v1/videos.
  • generate_audio: false is a 400 on rows where audio is always on.
  • Status values map to pending, in_progress, completed, failed and cancelled.

Keep the legacy path for

A source video_url request (Omni edit, Recast, Genjutsu) is only on the Video Router surface, so leave those calls on /v1/video-router/generate. Move everything else, and test one job per model first; the reference rules post explains the one place behavior differs from OpenRouter.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume