video-inspect 400: frames at and fps together on a Shorts hook
Send frames.at or frames.fps to Sume video-inspect, never both: both returns 400 video_inspect_frames_program_conflict. Pick at[] for a Shorts hook check.

If your Sume video-inspect call returns 400 video_inspect_frames_program_conflict, the frames object holds both at[] and fps. Keep one. For a Shorts hook review, at is usually the right choice because you want specific seconds, not a grid.
YouTube describes a Short as a square or vertical video up to 3 minutes (YouTube Help, read 2026-10-05). Whether a viewer stays is decided in the first seconds, so a quick still sample of the opening is a cheap preflight before you upload.
What the error means
The frames program is either explicit timestamps or a sample rate. The video inspect docs state that an object must contain only one of at[] or fps, and list the conflict as a stable refusal code. The machine-readable hint on the 400 is drop_frames_at_or_fps.
| frames value | Result |
|---|---|
| omitted | 8 mid-bin stills |
| false | probe only, no stills |
| { at: [..] } | 1 to 24 explicit timestamps, each 0 or more |
| { fps: n } | 0 < n <= 2, capped at 24 stills |
| { at, fps } | 400 video_inspect_frames_program_conflict |
A hook sample that works
The request below samples three instants in the opening seconds. The clip must already be a media.sume.com artifact of your workspace, so import it first with POST /v1/media-imports. The call needs an Idempotency-Key, and a 400 body is printed so you can read the code.
import json, os, urllib.error, urllib.request
body = {"video_url": "https://media.sume.com/artifacts/artf_demo/talk.mp4",
"frames": {"at": [0, 1, 2]}}
req = urllib.request.Request(
"https://api.sume.com/v1/video-inspect",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "hook-sample-001"})
try:
print(urllib.request.urlopen(req).status)
except urllib.error.HTTPError as err:
print(err.code, err.read().decode())Choosing at versus fps
- Use
atwhen you know the moments: second 0, the first caption cue, the first cut. - Use
fpsfor an even grid across a short clip, for example 1 to 2 stills per second. The ceiling is 24 stills per call either way. max_edge(64 to 2160, default 768) andformat(jpeg or png) work with both.seek: fasttrades timestamp accuracy for speed. For a hook check where each second matters, keep the defaultprecise.
Reading the error in a script
The 400 body carries the stable code and the next_action hint, so a wrapper can branch on them instead of parsing prose. Treat the code as the contract: when you see video_inspect_frames_program_conflict, delete one key and retry with the same logical request.
A small habit helps here: build the frames object from one config value, such as a list of seconds or a rate, so the two keys cannot meet in the same object by accident.
When not to use this
Video inspect reads one clip of at most 1800 seconds and returns at most 24 stills a call, so it is a sampler, not a scene detector. For a single frame at the full source size at one time, the docs point to video frames instead. Nothing here tells you how a platform will rank the video; it only shows you what is on screen at the seconds you chose.
Sources
Related posts
More in Media tools
- video-inspect frames needs at or fps: probe a Short with frames:false
video_inspect_frames_program_required means frames is an object with no at or fps. Omit frames for 8 stills, or pass frames:false to only probe a Short upload.
- Check an ad clip's aspect ratio with Sume video inspect stills
Sume video inspect returns stills with width and height. Send max_edge at the source edge, read the ratio, and catch a wrong-ratio clip before upload.
- Video inspect hdr flag: spot PQ or HLG clips before you submit
Sume video inspect returns probe.hdr, color_transfer and pix_fmt. Read them first and route HDR clips away from the video filter and exact trim.
- Video inspect 400 video_inspect_transcribe_required: the fix
Sending language_code, segmentation or duration_seconds to Sume video inspect without transcribe true returns a 400. Why, and the request that works.
Written by Sume