Video Router body to /v1/videos: frame_images, input_references

Map Video Router image_url, end_image_url and reference_image_urls to /v1/videos frame_images and input_references, with a Node converter.

5 min readSume
All posts

To move a request from POST /v1/video-router/generate to POST /v1/videos, rename the image fields. image_url becomes a frame_images entry with frame_type first_frame, end_image_url becomes one with last_frame, and reference_image_urls becomes input_references. The model ids are the same, so no id mapping is needed. mode is dropped; callback_url takes over from webhook mode. Edits that use video_url stay on the Video Router.

The field map

Both routes create the same jobs. The difference is the wire format: Video Router takes flat URL fields and returns a data envelope, while /v1/videos follows the OpenRouter shape, with image objects and a flat job response. Sume recommends /v1/videos for new integrations.

Video Router field to /v1/videos field (Sume docs, read 2026-10-09)
Video Router/v1/videosNote
image_urlframe_images[] with frame_type first_frameEntry is {type: image_url, image_url: {url}}
end_image_urlframe_images[] with frame_type last_frameOnly for models that list last_frame
reference_image_urls (up to 10)input_references[]Same {type, image_url} entry
reference_video_urls (up to 3, each up to 3 s)input_references[] video entriesNot converted by the sample; map by hand
video_url (edit)not availableKeep on Video Router; cannot combine with image or reference fields
mode: async / webhookcallback_urlHTTPS only

The converter

The function destructures the Video Router names, builds the image objects once, and leaves every other key (model, prompt, duration, resolution) untouched. It throws for video_url and for video or audio references, because those need a decision from a person about the entry format for the chosen model. It warns when both frames and references are present: if you send both fields, frame_images controls the mode and Sume runs image-to-video.

export function toVideosBody({ image_url, end_image_url, reference_image_urls, video_url, mode, ...rest }) {
  if (video_url) throw new Error("video_url edits stay on /v1/video-router/generate");
  if (rest.reference_video_urls || rest.reference_audio_urls) throw new Error("map video/audio references by hand");
  const img = (url) => ({ type: "image_url", image_url: { url } });
  const body = { ...rest };
  if (image_url) body.frame_images = [{ ...img(image_url), frame_type: "first_frame" }];
  if (end_image_url) {
    body.frame_images = [...(body.frame_images ?? []), { ...img(end_image_url), frame_type: "last_frame" }];
  }
  if (reference_image_urls?.length) body.input_references = reference_image_urls.map(img);
  if (body.frame_images && body.input_references) {
    console.warn("both sent: /v1/videos runs this as image-to-video; references are not used as frames");
  }
  return body;
}

console.log(
  JSON.stringify(
    toVideosBody({
      model: "gemini-omni-flash-1.1",
      prompt: "Product spins on a turntable",
      image_url: "https://example.com/first.png",
      end_image_url: "https://example.com/last.png",
      duration: 6,
      mode: "async",
    }),
    null,
    2,
  ),
);

Check the model before you convert

A model accepts a frame_type only if it is listed in supported_frame_images, and a reference type only if supported_input_references includes it. For example, Gemini Omni Flash 1.1 accepts image and video references but no audio, while the Seedance 2.x models, Wan 3.0 and the MiniMax H3 models accept audio and video references. Read those arrays from GET /v1/videos/models before sending.

The response also changes. A client that unwrapped data.job from Video Router must now read id, polling_url and status from the top level, and poll GET /v1/videos/{id}. The same job is still visible at GET /v1/jobs/{id}/status if you want the extra job fields.

Worked example: the body in the sample asks gemini-omni-flash-1.1 for a 6-second clip with a first and a last frame. After conversion it has two frame_images entries and no mode key, and the price is the same as before the move, 6 x 0.125 = $0.75 at 720p. Nothing about billing changes with the route; Sume bills the provider list price times 1.25 on both.

Because the two routes share one model vocabulary, a good migration test is to submit the same prompt through both with different Idempotency-Key values on a cheap model and compare usage.cost on the two completed jobs. They should match, since both routes create the same kind of job. If they do not, stop and read the request ids before you move traffic.

  • Test the converter on one body per model you use.
  • Do not send image_url or frame_images with video_url.
  • Keep Idempotency-Key; it is accepted on both routes.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume