Sume video submit 415: send JSON and pass images as URLs
POST /v1/videos answers 415 unsupported_media_type when the body is not application/json. How to read details.received_content_type and send frames as URLs.

If a video submit comes back 415 unsupported_media_type, the request body was not application/json. Sume's error table says so in one line, and the response carries details.received_content_type so you can see what your client actually sent. The fix is to post a JSON body with a Content-Type: application/json header and to pass any images as public HTTPS URLs inside that JSON, not as uploaded files.
This comes up when a team ports a client from a vendor that takes a multipart upload for the first frame. The Sume video request has no file field at all. Frames go in frame_images, and references go in input_references, each as an object holding a URL.
What the 415 tells you
The public error envelope has error.code, error.message, error.request_id and error.details. For a 415 the details name the content type that was received. Typical values to look for are multipart/form-data when a library built a form, text/plain when a client sent a string without a header, or an empty value when the header was dropped by a proxy.
| received_content_type shows | Likely cause | Fix |
|---|---|---|
| multipart/form-data | Client built a form to upload a frame | Send JSON; put the image URL in frame_images |
| text/plain | Body sent as a string without a header | Set Content-Type: application/json |
| application/x-www-form-urlencoded | HTTP library default for data= | Use the json= option or serialize first |
| Empty | A proxy or wrapper stripped the header | Set the header on the final hop |
Send the frame as a URL
The docs example for image-to-video sends a first frame as an object with type: "image_url", an image_url object with the url, and a frame_type. The troubleshooting section adds the condition that any reference image has to be reachable over public HTTPS and in a supported format. If your image lives only on a laptop or behind a login, host it first and send the link.
curl -X POST "https://api.sume.com/v1/videos" \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clip-001" \
-d '{"model":"seedance-2","prompt":"A character walking through a forest","frame_images":[{"type":"image_url","image_url":{"url":"https://example.com/first-frame.png"},"frame_type":"first_frame"}]}'Do not retry a 415 in a loop
A 415 is a request-shape problem, so the same bytes will fail the same way. Fix the header or the body, then resend with the same Idempotency-Key if you used one; the rejected call is a body-level error, so a corrected request is the first real submit. The error docs tell you not to retry unsafe submit requests without an Idempotency-Key, so keep sending one on every paid call.
Two neighbouring errors are easy to confuse with it. A 400 invalid_request means the body parsed but a field was wrong, for example a missing frame_type. A 413 payload_too_large means the body exceeded the configured API limit, which is another reason to send URLs and not inline image data.
A quick way to find the culprit
Log the request your client really sends, not the one you meant to send. Most HTTP libraries can print the final headers. If the Content-Type is missing or has a boundary suffix, you have a form post. If a gateway sits in front of your code, log on the far side of it too, since gateways sometimes rewrite or drop headers.
Once the 415 is gone you may see a 400 invalid_request next. That one is a body-level error, for example a frame_type that is missing or a field the model does not accept. Read error.details and fix the field named there.
Limits
The docs do not state the size limit behind the 413, and they do not say whether a data URL is accepted as an image URL, so this post recommends hosted HTTPS links without claiming that data URLs fail. If you want to test that, use a cheap development request and read the error.
Sources
Related posts
More in Developers
- Sume video sync submit: timed_out and capacity_exhausted flags
A sync submit returns 2xx with a job id even when the wait runs out. What the sync object says and what to do next.
- Sume webhooks plus a sweeper: recover jobs whose callback never came
Webhook delivery can fail after 10 attempts while the Sume job still finishes. Run a sweeper that polls jobs stuck non-terminal in your own table.
- SvelteKit +server.js endpoint to verify a Sume webhook signature
A SvelteKit +server.js POST handler gets a Fetch Request, so request.text() gives the raw body Sume signs. Verify the HMAC, then handle job.completed.
- Swap the TTS engine, keep the voice: Sonic 3.5 to 3.6 on Sume
Cartesia treats the TTS model and the voice as separate things. On Sume the model id and voice id are separate fields, so you can A/B 3.5 and 3.6 on one voice.
Written by Sume