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.

4 min readSume
All posts

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 program options in Sume video-inspect (docs read 2026-10-05)
frames valueResult
omitted8 mid-bin stills
falseprobe 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 at when you know the moments: second 0, the first caption cue, the first cut.
  • Use fps for 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) and format (jpeg or png) work with both.
  • seek: fast trades timestamp accuracy for speed. For a hook check where each second matters, keep the default precise.

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

All Media tools posts

Written by Sume