Check durations, frames and references against the Sume video catalog
Read supported_durations, supported_resolutions, supported_frame_images and supported_input_references from /v1/videos/models and refuse bad requests early.

You can catch most Sume video 400s in your own code by reading four fields per model from GET /v1/videos/models: supported_durations, supported_resolutions, supported_frame_images and supported_input_references. The script below checks a request against them and lists the problems. It does not create a job, and a valid result is not a guarantee, because some limits live only in the validation code.
What the catalog entry tells you
Each entry in data carries the fields in the table. They are the public contract of the OpenRouter-shaped route, and the Video Router uses the same ids and ranges.
| Field | Use it to check |
|---|---|
| supported_durations | The whole-second values you may send |
| supported_resolutions | 480p, 720p, 768p, 1080p and so on, per model |
| supported_aspect_ratios | 16:9, 9:16 and the rest |
| supported_frame_images | first_frame and last_frame allowed |
| supported_input_references | image_url, video_url, audio_url allowed |
| generate_audio | Whether the model can produce audio |
The script
It takes a model id and a description of the request, then prints each mismatch. Run it before you submit anything; it needs only your key in the environment and never prints it.
import os
import requests
hdr = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
r = requests.get("https://api.sume.com/v1/videos/models", headers=hdr, timeout=30)
r.raise_for_status()
cat = {m["id"]: m for m in r.json()["data"]}
def check(model, seconds, res, ratio, frames=(), refs=()):
m = cat.get(model)
if not m:
return ["unknown model id"]
bad = []
if seconds not in (m["supported_durations"] or []):
bad.append(f"duration {seconds} not in {m['supported_durations']}")
if res not in (m["supported_resolutions"] or []):
bad.append(f"resolution {res} not offered")
if ratio not in (m["supported_aspect_ratios"] or []):
bad.append(f"aspect {ratio} not offered")
bad += [f"frame {f} unsupported" for f in frames if f not in (m["supported_frame_images"] or [])]
bad += [f"reference {x} unsupported" for x in refs if x not in (m["supported_input_references"] or [])]
return bad
print(check("seedance-2.5", 12, "1080p", "9:16", ["last_frame"], ["audio_url"]))
What the preflight does not see
The catalog gives ranges and types. It does not give counts of references, clip lengths of reference files, pairing rules or the model-specific messages. Examples are 9 images, 3 videos and 3 audios on most models, a 15 second total on Wan's reference videos, a last frame needing a first frame, or Gemini Omni's three-second reference clips. Those are enforced by the validation on the create call, so treat them as a second layer and keep the related posts on each error close.
Where to run it
Put the check in the code path that builds the request, not in a separate tool. Cache the catalog for a short time to avoid a call per request, and refresh when you see an unexpected 400. If a model is gated by provider configuration, it can be absent from your list, which the check reports as an unknown id instead of a pricing or routing problem.
For tests, keep a small table of known good and known bad requests per model, run them through the check on every deploy, and compare the result with a live call once in a while. Drift between the catalog and your assumptions is the usual cause of surprise 400s: a model gains a longer range, or a new reference type appears, and your hard-coded limits are suddenly wrong in either direction.
Finally, make the output actionable. A list of problems is useful, but a corrected request is better. Where the fix is mechanical, such as clamping a duration to the nearest supported value, apply it and log the change; where it changes the creative intent, such as dropping a last frame, stop and ask.
Sources
Related posts
More in Developers
- Presigned URL expiry for image edit references on Sume
Sume downloads reference images from a public HTTPS URL. A presigned link that expires mid-queue gives input_media_unreachable. How to set a safe expiry.
- Preview a 3-minute Short with 24 stills, one every 7.5 seconds
Video frames returns up to 24 stills per call, which spaces a 180-second Short at 7.5 seconds. Build the at[] list in Python and submit it to /v1/video-frames.
- Price a multi-shot Wan 3.0 job first: Timeline plan is unbilled
Before you pay to join Wan 3.0 shots, call POST /v1/timeline-1.0/plan: it compiles the timeline and returns segment count, billable minutes and an estimate.
- Price guard: refuse a 30-second Seedance 2.5 render over your cap
Compute the Sume price of a clip from model, resolution and seconds before you POST, and refuse over a cap. Python code, the rates, and the 1080p cent rounding.
Written by Sume