Avatar video 400: script must include at least one word
A Sume talking-video request with an empty or whitespace-only script returns 400 invalid_request, with no job or ledger entry. Guard blank template fields.
A Sume avatar video request whose script is empty or only whitespace returns 400 invalid_request with the message Avatar video script must include at least one word. The check runs during validation, so no job is created and no usage is recorded: the API tests assert that the ledger has no entry after the response. You pay nothing for the failed call, and nothing is queued.
The request shape is on Generate avatar video, and the way Sume decides what to reserve before work starts is on Generation admission, both read 2026-10-05.
Where the word count matters
The rule is about words, not characters. A script of spaces, tabs or newlines has length but no words, and counts as empty. The duration limits that come right after it use the same word count to estimate how long the speech will run.
Whitespace is the trap. A script field that contains a newline from a spreadsheet cell, a single space from a form, or an empty paragraph from a rich text editor looks filled in to a human and to a simple truthiness check in many languages, since a string of spaces is non-empty. Trimming before the check is the habit that avoids sending it. The word count the API uses is the same one it applies to the duration estimate, so a script that passes the empty check is already a candidate for the 4 to 60 second window.
| Script | Result |
|---|---|
| "" (empty string) | 400 invalid_request, at least one word required |
| " \n " (whitespace only) | 400 invalid_request, at least one word required |
| One short sentence | Accepted if the estimated duration lands in the 4 to 60 second window |
| A 169-word script | 400: estimated at 64 seconds; maximum is 60 seconds |
Guard the template before the call
The usual cause is a template. A script like Hi {{first_name}}, {{offer_text}} is fine when both variables are set and an empty-looking request when a CRM row has blank fields. Render the template, trim it, and refuse to send an empty result.
import re
def render(template, values):
return re.sub(r"\{\{(\w+)\}\}", lambda m: values.get(m.group(1), ""), template)
def script_or_none(template, values):
text = " ".join(render(template, values).split())
return text if text else None
print(script_or_none("{{greeting}} {{offer}}", {"greeting": "Hi there,", "offer": "we saved you a seat."}))
print(script_or_none("{{greeting}} {{offer}}", {}))The same guard for scenes
The second call prints None, which is your signal to skip the row and log it instead of calling the API. Skipping is cheaper than sending, since even a free rejection costs a round trip and an error log line, and you probably want a person to look at a blank field.
Do the same for the multi-scene form. In video_inputs, a spoken scene needs a script or input_text, and a silent scene uses voice.type: silence with a duration. A scene with an empty script is not a silent scene; if you want a pause, say so with silence.
What comes next
Duration errors are the next ones you will meet. Sume estimates speech length from the word count and requires the total to fall in a window of 4 to 60 seconds, the same window the docs give for multi-scene requests. A long script is refused with a message that states the estimate and the maximum. For long content, see what to do past 60 seconds, and for what each length costs, see the price ladder.
A useful test for your own client is to feed it the three inputs from the table and assert that none of them reaches the network. The first two should be caught by your guard, and the third should pass through and be judged by the API. Writing that test once protects a campaign run from a data problem you would otherwise find only after a batch of 400 responses.
Do not retry a 400
Because the failed call is not a job, there is nothing to cancel, poll or reconcile. If your client retries on any 4xx, make 400 a permanent error and do not retry it: an empty script stays empty. Retries are for network failures, and the idempotency post covers how to do them without paying twice.
Sources
Related posts
More in Developers
- Avatar video 404 "Avatar was not found": handle from another workspace
A talking-video request with an unknown handle, or one from another workspace, returns 404 not_found and no job. Check the handle and the key's workspace.
- Avatar video 429: rate_limited vs queue_full, and which retry to use
Both are HTTP 429 on avatar video submit. rate_limited uses retry-after; queue_full means no accepted capacity. A small Python helper to pick the wait.
- Preview regenerate too early: 409 avatar_video_preview_busy, no charge
Calling regenerate on an avatar video preview that is still queued or processing returns 409 avatar_video_preview_busy and refunds the reservation. What to do.
- product_image 400: must be a fetchable public image URL
Sume avatar video rejects a product_image or scene image that is not a direct public image. What the 400 says, and why share links and HTML pages fail.
Written by Sume