video_filter_ops_empty 400: run video-filter/check before paying $0.02
video_filter_ops_empty means no ops[] and no filtergraph. Send one dim or crop op, or a filtergraph, and call the free /v1/video-filter/check first.

video_filter_ops_empty is a 400 from POST /v1/video-filter when the request has no ops[] and the filtergraph is missing or empty. Add at least one op (dim or crop) or a filters-only filtergraph. To find out before you are billed, post the same body to the free POST /v1/video-filter/check.
A typical cause is a script that builds ops conditionally, for example crop only when the source is not 9:16, and sends an empty list when it already is. YouTube describes Shorts as vertical video (YouTube Help, read 2026-10-05), so an already-vertical source legitimately needs no crop, and the correct behaviour is to skip the call.
What the program may contain
The video filter docs define the program: up to 8 ops[], applied before an optional filtergraph of at most 2048 characters and 32 filters. The source must be a media.sume.com clip of at most 300 seconds. The price is $0.02 per encode job, and the check is free.
| Input | Rule | Refusal |
|---|---|---|
| ops and filtergraph both absent | at least one required | video_filter_ops_empty |
| ops[] longer than 8 | maximum 8 | video_filter_too_many_ops |
| dim amount | in (0, 1] | video_filter_amount_out_of_range |
| crop fractions | x+width <= 1, side at least 0.05 | video_filter_crop_out_of_bounds |
| op other than dim or crop | use filtergraph | unsupported_filter_op |
Check, then submit
The check runs the same schema, whitelist and source preflight as the encode, but returns diagnostics instead of a 400, and it does not create a job or reserve credits. A valid response contains valid, encode: "not_run" and next_action of submit_video_filter or fix_program_and_recheck. This body crops the central 9:16 slice out of a 16:9 frame (0.3164 of the width at full height):
{
"video_url": "https://media.sume.com/artifacts/artf_demo/talk.mp4",
"ops": [
{ "op": "crop", "x": 0.3418, "y": 0, "width": 0.3164, "height": 1 }
]
}Guard in code
Skip the request when your program is empty, and call check when it is not. The simplest guard is to treat an empty list as a no-op in your own code, so the 400 never fires. Keep the check call in tests, where it costs nothing and catches crop fractions that fall outside the frame.
Reading the check result
The check is built for loops. Submit the draft program, read valid, and if it is false, read diagnostics[] and fix the named field. The compiled program.filters lists filter names only, never raw ffmpeg arguments, and a valid program also returns an estimate. Sume rejects client-sent vf, filter, ffmpeg, cmd, codec and crf fields with ffmpeg_fields_rejected, so the check also catches a client that tries to smuggle in its own arguments. Because it is free and needs no Idempotency-Key, it is safe to call on every build in CI.
Limits
A passing check does not guarantee the encode: bad expressions, memory or time can still fail on the worker and are returned as a structured job error. Video filter also never cuts a range; use video trim for that.
Sources
Related posts
More in Media tools
- Video filter unsupported_pixel_format: why a non-YUV source fails
Sume video filter only accepts sources with a YUV pixel format. What unsupported_pixel_format means, how to spot it with video inspect, and how to fix it.
- Video frames always returns 202: mode sync does not give a 200
Sume video frames pins async, so a submit returns 202 even with mode sync. How it differs from video inspect, which waits up to 30 s, and how to poll.
- Video frames: one frame with url null and the job still succeeds
In Sume video frames, a failed instant returns url null while the job completes. How to detect partial results and retry only the missing times.
- Video inspect stills come back 432x768: set max_edge 1920
Video inspect clamps stills to a 768 long edge by default, so a 1080x1920 clip returns 432x768 frames. Set max_edge up to 2160, or use video frames.
Written by Sume