generation_spend_cap_usd on a Format run: null is $500, 0 is a 400
On a Format run request, omit generation_spend_cap_usd for the Format cap, send a number up to 500, null for the $500 maximum. 0 or above 500 returns 400.

Leave generation_spend_cap_usd out and the run uses the Format's own cap. Send a number from above 0 up to 500 and that number is the cap of this run. Send null and the cap becomes the $500 platform maximum. Send 0, or anything above 500, and the request fails with 400. The docs give the reason for the zero rule: a run that cannot spend cannot deliver.
The five cases
Every Format has a generation spend cap, and a run can never spend more than its own effective cap. The request field only sets this run's ceiling. A Format that never named a cap reports the platform default of $400 in generation_spend_cap_usd_micros.
| You send | The run's cap |
|---|---|
| Nothing | The Format's cap |
| A number up to 500 | That number. A value above the Format's own cap is accepted and not clamped |
null | The platform maximum, $500. It lifts the ceiling but does not remove it |
0 | 400 |
| Above 500 | 400 |
Two details follow from the table. First, a request can raise the cap above what the Format owner set, so the cap is a control of the caller. Treat it as a budget you enforce in your own code. Second, null is not the same as omitting the field: omitting keeps the Format's cap, while null raises the run to $500. Serializers that drop undefined and keep null will therefore change spend, so check what your JSON library emits.
Build the field safely
The helper below returns the body fragment for each case and throws for values the API would reject, so the mistake shows up in your tests rather than as a 400 in production. It performs no network calls.
// Per-run ceiling for generation_spend_cap_usd, per the Calling a Format rules.
export function capField(value) {
if (value === undefined) return {}; // the Format's own cap applies
if (value === null) return { generation_spend_cap_usd: null }; // platform maximum, $500
if (!(value > 0 && value <= 500)) {
throw new RangeError("generation_spend_cap_usd must be above 0 and at most 500");
}
return { generation_spend_cap_usd: value };
}
console.log(capField(undefined), capField(null), capField(25));
try { capField(0); } catch (e) { console.log(e.message); }
try { capField(501); } catch (e) { console.log(e.message); }What happens at the cap
- The wallet is checked at create.
402 insufficient_credits(next_action: add_funds) or402 organization_wallet_not_provisionedmeans nothing ran. - The cap is checked during the run. When it is reached, the run ends as
failed, andusageshows how near to the cap it got. - You still pay for generation that finished before a failure. The docs do not refund it.
- Watch headroom on the receipt with
usage.cap. It giveslimit_usd_micros,counted_usd_microsandremaining_usd_micros. - Sizing: the docs say production long-form runs usually have caps near $120, and a single-scene retry a few dollars. Use your own receipts to pick your numbers.
Where a cap belongs in a pipeline
Set the cap per job type, not per account. A retry of one scene and a full long-form video should not share a number. When an item fails on the cap, raise the value for the next run instead of retrying the same one, since the same plan will hit the same ceiling. Log the cap you sent next to the run id and the Idempotency-Key, so a later failed receipt can be explained without guessing which ceiling applied. The numbers on this page come from the Calling a Format and Errors and spend pages.
Sources
Related posts
More in Developers
- Get a transcript of an AI video before TikTok Shop review
Run Sume video inspect with transcribe: true to read what an AI video says, at $0.01 per audio minute plus compute, before you post a TikTok Shop clip.
- Get transcript text from a captioned video: caption jobs return none
A Sume caption job returns the burned video, not the transcript. For text and word times run STT or video inspect with transcribe. Prices and a recipe.
- Go: cancel the rest of a batch after the first 402 on Sume
A Go fan-out that stops launching Wan 3.0 submits the moment one returns 402 insufficient_credits, using context.WithCancel and a 2-slot semaphore.
- Go: a context timeout stops waiting on a Sume job, not the job
context.WithTimeout cancels your status read, never the generation. A 29-line Go loop shows the DeadlineExceeded branch, and why the job id must be stored.
Written by Sume